Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 86 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,27 @@

A Ruby gem for interacting with the NovaCloud Open Platform API. This client provides:

- Simple configuration with `app_key`, `app_secret`, and `service_domain`
- Automatic authentication headers via Faraday middleware
- Typed exception hierarchy for HTTP errors
- JSON request/response handling
- Resource-based API wrappers with typed response objects
- Manages configuration once (`app_key`, `app_secret`, `service_domain`).
- Handles authentication headers automatically via Faraday middleware.
- Maps HTTP errors to a typed exception hierarchy.
- Normalizes GET/POST payloads and parses JSON responses.

## Resource Overview

The gem currently implements the following NovaCloud API resources:

- **Players**: `list`, `statuses`, `running_status` - Player management and status queries
- **Control**: `brightness`, `volume`, `video_source`, `screen_power`, `screen_status`, `screenshot`, `reboot`, `request_result` - Real-time player control commands
- **Screens** (VNNOXCare): `list`, `monitor`, `detail` - Screen device status monitoring
- **Logs**: `control_history` - Control command execution history

**Note**: The gem focuses on the most commonly used endpoints. Additional endpoints like Solutions (emergency insertion, offline export), Scheduled Control, and Play Logs are not yet implemented but can be added based on demand.
Sprint 02 expands on this foundation with dedicated resource helpers (`client.players`, `client.control`, `client.scheduled_control`, `client.solutions`) and typed response objects (e.g., `NovacloudClient::Objects::Player`).

## Installation

Add this line to your application's Gemfile:

```ruby
gem 'novacloud_client'
```

And then execute:

```bash
bundle install
```
## Resource Overview

Or install it yourself as:
- **Players**: `list`, `statuses`, `running_status`
- **Control**: `brightness`, `volume`, `video_source`, `screen_power`, `screen_status`, `screenshot`, `reboot`, `ntp_sync`, `synchronous_playback`, `request_result`
- **Scheduled Control**: `screen_status`, `reboot`, `volume`, `brightness`, `video_source`
- **Solutions**: `emergency_page`, `cancel_emergency`, `common_solution`, `offline_export`, `set_over_spec_detection`, `program_over_spec_detection`
- **Screens** (VNNOXCare): `list`, `monitor`, `detail`
- **Logs**: `control_history`

```bash
gem install novacloud_client
```
> **Heads-up:** NovaCloud's public API docs (as of October 2025) do not expose
> "material" endpoints for uploading, listing, or deleting media assets. This
> client therefore expects assets to be hosted already (either uploaded via the
> VNNOX UI or served from your own CDN) and referenced by URL in solution
> payloads.

## Quick Start

Expand All @@ -61,7 +46,28 @@ statuses.each do |status|
puts "Player #{status.player_id}: online=#{status.online?}"
end

# Control player brightness
queue = client.players.config_status(
player_ids: players.map(&:player_id),
notice_url: "https://example.com/status-webhook"
)

client.control.ntp_sync(
player_ids: players.map(&:player_id),
server: "ntp1.aliyun.com",
enable: true
)

client.scheduled_control.brightness(
player_ids: players.map(&:player_id),
schedules: {
start_date: Date.today.strftime("%Y-%m-%d"),
end_date: (Date.today + 30).strftime("%Y-%m-%d"),
exec_time: "07:00:00",
type: 0,
value: 55
}
)

request = client.control.brightness(
player_ids: players.map(&:player_id),
brightness: 80,
Expand All @@ -74,12 +80,54 @@ puts "All successful? #{result.all_successful?}"

# List screens (VNNOXCare)
screens = client.screens.list(status: 1)
puts "Screen: #{screens.first.name}" if screens.any?
puts screens.first.name

client.solutions.emergency_page(
player_ids: [first_player.player_id],
attribute: { duration: 20_000, normal_program_status: "PAUSE", spots_type: "IMMEDIATELY" },
page: {
name: "urgent-alert",
widgets: [
{
type: "PICTURE",
z_index: 1,
duration: 10_000,
url: "https://example.com/alert.png",
layout: { x: "0%", y: "0%", width: "100%", height: "100%" }
}
]
}
)

offline_bundle = client.solutions.offline_export(
program_type: 1,
plan_version: "V2",
pages: [
{
name: "main",
widgets: [
{ type: "PICTURE", md5: "abc", url: "https://cdn.example.com/img.jpg" }
]
}
]
)

puts offline_bundle.plan_json.url

over_spec_result = client.solutions.program_over_spec_detection(
player_ids: [first_player.player_id],
pages: [
{
page_id: 1,
widgets: [
{ widget_id: 1, type: "VIDEO", url: "https://cdn.example.com/video.mp4", width: "3840", height: "2160" }
]
}
]
)

# View control command history
logs = client.logs.control_history(player_id: players.first.player_id)
logs.each do |log|
puts "#{log.time}: #{log.task_name} - #{log.status}"
if over_spec_result.items.any?(&:over_spec?)
warn "Program exceeds specifications"
end
```

Expand Down
10 changes: 10 additions & 0 deletions lib/novacloud_client/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
require_relative "middleware/error_handler"
require_relative "resources/players"
require_relative "resources/control"
require_relative "resources/scheduled_control"
require_relative "resources/solutions"
require_relative "resources/screens"
require_relative "resources/logs"

Expand Down Expand Up @@ -49,6 +51,14 @@ def control
@control ||= Resources::Control.new(self)
end

def scheduled_control
@scheduled_control ||= Resources::ScheduledControl.new(self)
end

def solutions
@solutions ||= Resources::Solutions.new(self)
end

def screens
@screens ||= Resources::Screens.new(self)
end
Expand Down
80 changes: 80 additions & 0 deletions lib/novacloud_client/objects/solutions/offline_export_result.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# frozen_string_literal: true

require_relative "../base"

module NovacloudClient
module Objects
module Solutions
# Represents the payload returned from the offline export endpoint.
class OfflineExportResult < Base
attr_reader :display_solutions, :play_relations, :play_solutions,
:playlists, :schedule_constraints, :plan_json

def display_solutions=(value)
@display_solutions = build_artifact(value)
end

def play_relations=(value)
@play_relations = build_artifact(value)
end

def play_solutions=(value)
@play_solutions = build_artifact(value)
end

def playlists=(value)
@playlists = build_artifact(value)
end

def schedule_constraints=(value)
@schedule_constraints = build_artifact(value)
end

def plan_json=(value)
@plan_json = build_artifact(value)
end

private

def build_artifact(value)
return nil if value.nil?

if value.is_a?(Array)
value.map { |artifact| Artifact.new(artifact) }
else
Artifact.new(value)
end
end

# Represents a downloadable artifact (JSON, playlist, etc.) returned by the export.
class Artifact < Base
attr_accessor :md5, :file_name, :url, :program_name

attr_writer :support_md5_checkout

def support_md5_checkout
return nil if @support_md5_checkout.nil?

!!@support_md5_checkout
end

def support_md5_checkout?
support_md5_checkout
end

# Legacy camelCase accessors for backward compatibility with existing clients.
# Legacy camelCase accessors for backward compatibility with existing clients.
# rubocop:disable Naming/PredicatePrefix
def is_support_md5_checkout?
support_md5_checkout?
end

def is_support_md5_checkout=(value)
self.support_md5_checkout = value
end
# rubocop:enable Naming/PredicatePrefix
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# frozen_string_literal: true

require_relative "../base"

module NovacloudClient
module Objects
module Solutions
# Represents the payload returned when checking program over-specification.
class OverSpecDetectionResult < Base
attr_accessor :logid, :status
attr_reader :items

def initialize(attributes = {})
@items = []
super
end

def data=(value)
@items = Array(value).map { |entry| Item.new(entry) }
end

alias data items

# Entry describing over-specification findings for a specific set of players.
class Item < Base
attr_accessor :over_spec_type
attr_reader :over_spec, :player_ids

def initialize(attributes = {})
@details = []
super
end

def over_spec=(value)
@over_spec = !!value
end

def over_spec?
over_spec
end

def player_ids=(value)
@player_ids = Array(value)
end

def over_spec_detail=(value)
@details = Array(value).map { |detail| Detail.new(detail) }
end

def details
@details ||= []
end

# Detail record for a widget that exceeds specifications.
class Detail < Base
attr_accessor :page_id, :widget_id
attr_reader :over_spec_error_code, :recommendation

def over_spec_error_code=(value)
@over_spec_error_code = Array(value)
end

def over_spec_error_codes
@over_spec_error_code
end

def recommend=(value)
@recommendation = value ? Recommendation.new(value) : nil
end

# Suggested adjustments for bringing a widget within spec.
class Recommendation < Base
attr_accessor :width, :height, :postfix, :fps, :byte_rate, :codec
end
end
end
end
end
end
end
32 changes: 32 additions & 0 deletions lib/novacloud_client/objects/solutions/publish_result.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

require_relative "../base"

module NovacloudClient
module Objects
module Solutions
# Represents the publish result structure returned by solution endpoints.
class PublishResult < Base
def success=(value)
@successful = Array(value).compact
end

def fail=(value)
@failed = Array(value).compact
end

def all_successful?
failed.empty?
end

def failed
@failed ||= []
end

def successful
@successful ||= []
end
end
end
end
end
Loading