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

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}