Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

nightsky.gno

7.91 Kb · 263 lines
  1// Package nightsky holds the shared types and single-telescope logic for the
  2// GnoNightSky telescope network.
  3//
  4// Note on panics: although this lives under p/, it is application-specific
  5// logic for the NightSky realms rather than a general-purpose reusable
  6// library. It deliberately panics on invalid input / failed preconditions
  7// (the assert-like style described in effective-gno) so the wrapping realms
  8// don't have to re-check every error. Callers that want softer handling
  9// should validate before calling.
 10package nightsky
 11
 12import (
 13	"time"
 14
 15	"gno.land/p/nt/avl/v0"
 16)
 17
 18// TelescopeConfig contains the settings and configuration for a telescope
 19type TelescopeConfig struct {
 20	Name       string    // Name of the telescope
 21	Model      string    // Telescope model (e.g., "Seestar S50")
 22	Latitude   float64   // Geographic location
 23	Longitude  float64   // Geographic location
 24	Owner      string    // Owner's gno address
 25	RealmPath  string    // Path to the telescope's realm (e.g., "/r/username/telescope")
 26	Status     string    // Current status: "online", "offline", "busy", "error"
 27	LastUpdate time.Time // Last status update
 28}
 29
 30// AccessRule defines who can access a telescope
 31type AccessRule struct {
 32	AllowedAddress string    // Gno address allowed to access
 33	GrantedBy      string    // Who granted access
 34	GrantedAt      time.Time // When access was granted
 35	ExpiresAt      time.Time // When access expires (zero time = never)
 36}
 37
 38// TelescopeCommand represents a command sent to a telescope
 39type TelescopeCommand struct {
 40	CommandType string    // "capture" or "stop"
 41	TargetRA    float64   // Right Ascension (for pointing/tracking)
 42	TargetDec   float64   // Declination (for pointing/tracking)
 43	Exposure    int       // Exposure time in seconds (for capture)
 44	RequestedBy string    // Who requested the command
 45	RequestedAt time.Time // When the command was requested
 46}
 47
 48// CaptureResult represents the result of an image capture
 49type CaptureResult struct {
 50	ImageURL    string    // URL to the captured image (e.g., Imgur)
 51	TargetRA    float64   // Right Ascension of target
 52	TargetDec   float64   // Declination of target
 53	Exposure    int       // Exposure time in seconds
 54	CapturedBy  string    // Who captured the image
 55	CapturedAt  time.Time // When the image was captured
 56	TelescopeID string    // Which telescope captured it
 57}
 58
 59// NewTelescopeConfig creates a new telescope configuration
 60func NewTelescopeConfig(name, model string, lat, lon float64, owner, realmPath string) TelescopeConfig {
 61	return TelescopeConfig{
 62		Name:       name,
 63		Model:      model,
 64		Latitude:   lat,
 65		Longitude:  lon,
 66		Owner:      owner,
 67		RealmPath:  realmPath,
 68		Status:     "offline",
 69		LastUpdate: time.Now(),
 70	}
 71}
 72
 73// UpdateStatus updates the telescope status
 74func (t *TelescopeConfig) UpdateStatus(status string) {
 75	t.Status = status
 76	t.LastUpdate = time.Now()
 77}
 78
 79// IsAccessAllowed checks if an address has a current (non-expired) access rule.
 80// rules is an avl.Tree keyed by allowed address holding AccessRule values.
 81func IsAccessAllowed(address string, rules *avl.Tree) bool {
 82	if !rules.Has(address) {
 83		return false
 84	}
 85	rule := rules.Get(address).(AccessRule)
 86	return rule.ExpiresAt.IsZero() || time.Now().Before(rule.ExpiresAt)
 87}
 88
 89// ValidateCommand validates a telescope command
 90func ValidateCommand(cmd TelescopeCommand, config TelescopeConfig) {
 91	switch cmd.CommandType {
 92	case "capture", "stop":
 93	default:
 94		panic("invalid command type: must be capture or stop")
 95	}
 96
 97	if cmd.CommandType == "capture" {
 98		if cmd.TargetRA < 0 || cmd.TargetRA >= 24 {
 99			panic("invalid RA: must be between 0 and 24 hours")
100		}
101		if cmd.TargetDec < -90 || cmd.TargetDec > 90 {
102			panic("invalid Dec: must be between -90 and 90 degrees")
103		}
104		if cmd.Exposure < 1 || cmd.Exposure > 300 {
105			panic("invalid exposure time: must be between 1 and 300 seconds")
106		}
107	}
108}
109
110// maxCaptureHistory is the number of captures a single telescope keeps locally.
111const maxCaptureHistory = 100
112
113// TelescopeRealm manages a single telescope's state and operations
114type TelescopeRealm struct {
115	Config       *TelescopeConfig
116	AccessRules  *avl.Tree // key: allowed address -> AccessRule
117	CommandQueue []TelescopeCommand
118	Captures     *CaptureFeed
119	OwnerAddress string
120}
121
122// NewTelescopeRealm creates a new telescope realm instance
123func NewTelescopeRealm(config *TelescopeConfig) *TelescopeRealm {
124	return &TelescopeRealm{
125		Config:       config,
126		AccessRules:  avl.NewTree(),
127		CommandQueue: []TelescopeCommand{},
128		Captures:     NewCaptureFeed(maxCaptureHistory),
129		OwnerAddress: config.Owner,
130	}
131}
132
133// GrantAccess allows the owner to grant access to another user. Re-granting to
134// an existing address overwrites the previous rule.
135func (r *TelescopeRealm) GrantAccess(caller, address string, durationDays int) {
136	r.checkOwner(caller)
137
138	if address == "" {
139		panic("address cannot be empty")
140	}
141
142	expiresAt := time.Time{} // Never expires by default
143	if durationDays > 0 {
144		expiresAt = time.Now().Add(time.Duration(durationDays) * 24 * time.Hour)
145	}
146
147	rule := AccessRule{
148		AllowedAddress: address,
149		GrantedBy:      r.OwnerAddress,
150		GrantedAt:      time.Now(),
151		ExpiresAt:      expiresAt,
152	}
153
154	r.AccessRules.Set(address, rule)
155}
156
157// RevokeAccess removes access for a specific address
158func (r *TelescopeRealm) RevokeAccess(caller, address string) {
159	r.checkOwner(caller)
160	r.AccessRules.Remove(address)
161}
162
163// SubmitCommand allows authorized users to submit telescope commands
164func (r *TelescopeRealm) SubmitCommand(caller, commandType string, targetRA, targetDec float64, exposure int) {
165	// Check if caller has access (owner always has access)
166	if caller != r.OwnerAddress {
167		if !IsAccessAllowed(caller, r.AccessRules) {
168			panic("access denied: you don't have permission to use this telescope")
169		}
170	}
171
172	// Create command
173	cmd := TelescopeCommand{
174		CommandType: commandType,
175		TargetRA:    targetRA,
176		TargetDec:   targetDec,
177		Exposure:    exposure,
178		RequestedBy: caller,
179		RequestedAt: time.Now(),
180	}
181
182	// Validate command
183	ValidateCommand(cmd, *r.Config)
184
185	// Add to queue
186	r.CommandQueue = append(r.CommandQueue, cmd)
187
188	// Update telescope status to busy
189	r.Config.UpdateStatus("busy")
190}
191
192// GetNextCommand returns the next command in the queue (for telescope controller)
193func (r *TelescopeRealm) GetNextCommand(caller string) TelescopeCommand {
194	r.checkOwner(caller)
195
196	if len(r.CommandQueue) == 0 {
197		panic("no commands in queue")
198	}
199
200	cmd := r.CommandQueue[0]
201	r.CommandQueue = r.CommandQueue[1:]
202
203	return cmd
204}
205
206// UpdateStatus allows the owner to update telescope status
207func (r *TelescopeRealm) UpdateStatus(caller string, status string) {
208	r.checkOwner(caller)
209	r.Config.UpdateStatus(status)
210}
211
212// RecordCapture allows the owner to record a capture result
213func (r *TelescopeRealm) RecordCapture(caller, imageURL string, targetRA, targetDec float64, exposure int, capturedBy string) {
214	r.checkOwner(caller)
215
216	if imageURL == "" {
217		panic("image URL cannot be empty")
218	}
219
220	capture := CaptureResult{
221		ImageURL:    imageURL,
222		TargetRA:    targetRA,
223		TargetDec:   targetDec,
224		Exposure:    exposure,
225		CapturedBy:  capturedBy,
226		CapturedAt:  time.Now(),
227		TelescopeID: r.Config.Name,
228	}
229
230	r.Captures.Add(capture)
231
232	// Update status back to online
233	r.Config.UpdateStatus("online")
234}
235
236// ClearCommandQueue clears all pending commands (emergency stop)
237func (r *TelescopeRealm) ClearCommandQueue(caller string) {
238	r.checkOwner(caller)
239	r.CommandQueue = []TelescopeCommand{}
240	r.Config.UpdateStatus("online")
241}
242
243// GetCommandCount returns the number of pending commands
244func (r *TelescopeRealm) GetCommandCount() int {
245	return len(r.CommandQueue)
246}
247
248// GetCaptureCount returns the number of stored captures
249func (r *TelescopeRealm) GetCaptureCount() int {
250	return r.Captures.Len()
251}
252
253// GetAccessRuleCount returns the number of access rules
254func (r *TelescopeRealm) GetAccessRuleCount() int {
255	return r.AccessRules.Size()
256}
257
258// checkOwner verifies the caller is the owner
259func (r *TelescopeRealm) checkOwner(caller string) {
260	if caller != r.OwnerAddress {
261		panic("access restricted: owner only")
262	}
263}