Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
5f319f6
feat(tools): update icons in bottom navigation bar to use Phosphor icons
PaulGD03 Oct 13, 2025
128efb4
feat(util): ThumnailCache: enhance cache clearing methods for improve…
PaulGD03 Oct 13, 2025
6ca7aad
feat(util): StatusCard: add showPercentage option to control progress…
PaulGD03 Oct 13, 2025
bcd14f3
refactor(util): ConnectionError: update header comments and simplify …
PaulGD03 Oct 13, 2025
c869f3c
feat(backend_service): implement NotificationProvider and Notificatio…
PaulGD03 Oct 13, 2025
6a3cf5c
fix(install_script): update downsampled resolution to 210x210 for cor…
PaulGD03 Oct 13, 2025
ed73d53
feat(materials): add MaterialsScreen with navigation and placeholder …
PaulGD03 Oct 13, 2025
860b3d4
fix(files): DetailsScreen: update title and remove redundant name car…
PaulGD03 Oct 13, 2025
e436425
feat(util): implement LayerPreviewCache for in-memory caching of 2D l…
PaulGD03 Oct 13, 2025
ee189c8
refactor(status): simplify NanoDLP mode check by using isNanoDlpMode …
PaulGD03 Oct 13, 2025
b3b5b8b
feat(backend_service): nanodlp: thumbnail: maintain aspect ratio, 2d …
PaulGD03 Oct 13, 2025
991f6f3
feat(backend_service): add getNotifications and disableNotification m…
PaulGD03 Oct 13, 2025
9ce9af5
feat(backend_service: nanodlp): refine printing state logic to avoid …
PaulGD03 Oct 13, 2025
6040543
feat(status): implement 2D layer preview with prefetching and caching
PaulGD03 Oct 13, 2025
f55af3e
feat(main): add NotificationProvider and NotificationWatcher for impr…
PaulGD03 Oct 13, 2025
0d71fd1
feat(tests): add fake Odyssey client implementations and thumbnail ca…
PaulGD03 Oct 13, 2025
388264d
dev(backend_service: nanodlp): add NanoDlpSimulatedClient for local d…
PaulGD03 Oct 14, 2025
096abdc
feat(scripts/install_orion_athena2): add thumbnail-cache clearing and…
PaulGD03 Oct 14, 2025
c1caf1c
refactor(glasser): simplify GlassPlatformConfig logic and tighten docs
PaulGD03 Oct 14, 2025
77294ee
feat(glasser): add floatingSurface to control blur usage, support cus…
PaulGD03 Oct 14, 2025
cf8e083
feat(glasser): add GlassButton tinting and apply semantic tints acros…
PaulGD03 Oct 14, 2025
a5f933e
fix(backend_service: nanodlp): replace 'confirm' with 'close' for kli…
PaulGD03 Oct 14, 2025
6266d1a
refactor(backend_service: nanodlp): make _totalLayers final in NanoDl…
PaulGD03 Oct 14, 2025
279de41
refactor(backend_service: files_provider): simplify usbAvailable log …
PaulGD03 Oct 14, 2025
87dad31
refactor(util: sl1_thumbnail): remove unused dart:typed_data import
PaulGD03 Oct 14, 2025
6057aab
refactor(util: status_card): replace GlassCard with standard Card and…
PaulGD03 Oct 14, 2025
a3546e6
refactor(scripts: install_orion_athena2): use vendor colorSeed and ad…
PaulGD03 Oct 14, 2025
150b7cb
feat(backend_service: nanodlp): implement manualCure and displayTest
PaulGD03 Oct 15, 2025
06e8fb2
fix(tools: exposure_screen): respect device exposure delay and correc…
PaulGD03 Oct 15, 2025
7e1dfc6
fix(scripts: install_orion_athena2): point ORION_URL to BRANCH_athena…
PaulGD03 Oct 15, 2025
19ff307
fix(util: error_handling): make dialogs modal and order notification …
PaulGD03 Oct 15, 2025
a971dea
feat(backend_service): add BackendClient abstraction and NanoDLP helpers
PaulGD03 Oct 15, 2025
5ea6e12
fix(files: details_screen): show filename + modified time in AppBar, …
PaulGD03 Oct 15, 2025
692b1c8
feat(backend_service): add AnalyticsProvider to fetch telemetry via S…
PaulGD03 Oct 15, 2025
0319eac
feat(status_screen): display resin temperature in AppBar, tidy landsc…
PaulGD03 Oct 15, 2025
3cc1e0c
feat(backend_service): add getAnalyticValue and refactor AnalyticsPro…
PaulGD03 Oct 15, 2025
86a7d09
feat(tools): add Force Sensor screen, wire into ToolsScreen; register…
PaulGD03 Oct 15, 2025
d95bcbc
feat(settings): add Raw Force Sensor toggle to developer settings and…
PaulGD03 Oct 16, 2025
04f20d6
feat(glasser): add margin prop to GlassButton and tweak glass visuals
PaulGD03 Oct 16, 2025
7b65e2c
style(settings): reduce vertical padding on UI settings screen
PaulGD03 Oct 16, 2025
01001d3
perf(backend_service): increase analytics polling frequency to 15 Hz
PaulGD03 Oct 16, 2025
1c917da
feat(backend_service): add tareForceSensor API and manual tare support
PaulGD03 Oct 16, 2025
7c46050
feat(force_screen): integrate manual tare via ManualProvider; add dev…
PaulGD03 Oct 16, 2025
9773d51
feat: Startup Screen (#38)
PaulGD03 Oct 21, 2025
becb39b
Merge branch 'nanodlp_basic_support' into athena_features
PaulGD03 Oct 21, 2025
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
12 changes: 12 additions & 0 deletions assets/images/COPYRIGHT_NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
ORA Logos Copyright Notice

The ORA logos (including, but not limited to, "open_resin_alliance_logo_darkmode.png") are NOT covered by the Apache License, Version 2.0 that applies to this project. The logos are protected by copyright and may also be protected as trademarks. All rights are reserved by Ada Phillips.

You MAY NOT copy, distribute, modify, publish, display, or otherwise use the logos except as explicitly permitted in writing by the copyright/trademark owner.

Repository source code and other content are licensed under the Apache License, Version 2.0 unless otherwise stated. This notice applies only to the logo artwork referenced above.

To request permission to use the ORA logos, contact the project maintainers or:
contact@openresin.org

© 2025 Open Resin Alliance. All rights reserved.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
105 changes: 105 additions & 0 deletions lib/backend_service/backend_client.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Orion - Backend Client
* Copyright (C) 2025 Open Resin Alliance
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import 'dart:typed_data';
import 'dart:async';

/// Minimal abstraction over the Backend API used by providers. This allows
/// swapping implementations (real HTTP client, mock, or unit-test doubles)
/// while keeping providers free from direct dependency on ApiService.
abstract class BackendClient {
Future<Map<String, dynamic>> listItems(
String location, int pageSize, int pageIndex, String subdirectory);

Future<bool> usbAvailable();

Future<Map<String, dynamic>> getFileMetadata(
String location, String filePath);

Future<Map<String, dynamic>> getConfig();

Future<String> getBackendVersion();

Future<Uint8List> getFileThumbnail(
String location, String filePath, String size);

Future<void> startPrint(String location, String filePath);

Future<Map<String, dynamic>> deleteFile(String location, String filePath);

// Status-related
Future<Map<String, dynamic>> getStatus();

/// Stream of status updates from the server. Implementations may expose
/// an SSE / streaming endpoint. Each emitted Map corresponds to a JSON
/// object parsed from the stream's data payloads.
Stream<Map<String, dynamic>> getStatusStream();

/// Fetch recent notifications from the backend. Returns a list of JSON
/// objects representing notifications. Some backends (e.g. NanoDLP)
/// expose a `/notification` endpoint that returns an array.
Future<List<Map<String, dynamic>>> getNotifications();

/// Disable / acknowledge a notification on the backend when supported.
/// The timestamp argument is the numeric timestamp provided by the
/// notification payload (e.g. NanoDLP uses an integer timestamp).
Future<void> disableNotification(int timestamp);

// Print control
Future<void> cancelPrint();
Future<void> pausePrint();
Future<void> resumePrint();

// Manual controls and hardware commands
Future<Map<String, dynamic>> move(double height);

/// Send a relative Z move in millimeters (positive = up, negative = down).
/// This maps directly to the device's relative move endpoints when
/// available (e.g. NanoDLP /z-axis/move/.../micron/...).
Future<Map<String, dynamic>> moveDelta(double deltaMm);

/// Whether the client supports a direct "move to top limit" command.
Future<bool> canMoveToTop();
Future<bool> canMoveToFloor();

/// Move the Z axis directly to the device's top limit if supported.
Future<Map<String, dynamic>> moveToTop();
Future<Map<String, dynamic>> moveToFloor();
Future<Map<String, dynamic>> manualCure(bool cure);
Future<Map<String, dynamic>> manualHome();
Future<Map<String, dynamic>> manualCommand(String command);
Future<Map<String, dynamic>> emergencyStop();
Future<void> displayTest(String test);

/// Fetch a specific 2D layer PNG from a NanoDLP-style plates endpoint.
/// plateId is the numeric plate identifier and layer is the layer index
/// (as reported by the backend). Implementations that don't support this
/// may return a placeholder image or empty bytes.
Future<Uint8List> getPlateLayerImage(int plateId, int layer);

/// Fetch recent analytics entries. `n` requests the last N entries.
/// Returns a list of JSON objects with keys like 'ID', 'T', 'V'.
Future<List<Map<String, dynamic>>> getAnalytics(int n);

/// Fetch a single analytic value by metric id (e.g. /analytic/value/6).
/// Returns the raw value (number or string) or null on failure.
Future<dynamic> getAnalyticValue(int id);

/// Tare the force sensor if supported by the backend.
/// Returns a boolean indicating success or failure.
Future<dynamic> tareForceSensor();
}
64 changes: 53 additions & 11 deletions lib/backend_service/backend_service.dart
Original file line number Diff line number Diff line change
@@ -1,29 +1,49 @@
/*
* Orion - Backend Service
* Copyright (C) 2025 Open Resin Alliance
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import 'dart:typed_data';
import 'package:orion/backend_service/odyssey/odyssey_client.dart';
import 'package:orion/backend_service/backend_client.dart';
import 'package:orion/backend_service/odyssey/odyssey_http_client.dart';
import 'package:orion/backend_service/nanodlp/nanodlp_http_client.dart';
import 'package:orion/backend_service/nanodlp/helpers/nano_simulated_client.dart';
import 'package:orion/util/orion_config.dart';

/// BackendService is a small façade that selects a concrete
/// `OdysseyClient` implementation at runtime. This centralizes the
/// `BackendClient` implementation at runtime. This centralizes the
/// point where an alternative backend implementation (different API)
/// can be swapped in without changing providers or UI code.
class BackendService implements OdysseyClient {
final OdysseyClient _delegate;
class BackendService implements BackendClient {
final BackendClient _delegate;

/// Default constructor: picks the concrete implementation based on
/// configuration (or defaults to the HTTP adapter).
BackendService({OdysseyClient? delegate})
BackendService({BackendClient? delegate})
: _delegate = delegate ?? _chooseFromConfig();

static OdysseyClient _chooseFromConfig() {
static BackendClient _chooseFromConfig() {
try {
final cfg = OrionConfig();
final backend = cfg.getString('backend', category: 'advanced');
if (backend == 'nanodlp') {
// Developer-mode simulated backend flag (developer.simulated = true)
final simulated = cfg.getFlag('simulated', category: 'developer');
if (simulated) {
return NanoDlpSimulatedClient();
}
if (cfg.isNanoDlpMode()) {
// Return the NanoDLP adapter when explicitly requested in config.
// Add a small log to aid debugging in cases where config isn't applied.
// Note: avoid bringing logging package into this file if not used
return NanoDlpHttpClient();
}
} catch (_) {
Expand All @@ -32,7 +52,7 @@ class BackendService implements OdysseyClient {
return OdysseyHttpClient();
}

// Forward all OdysseyClient methods to the selected delegate.
// Forward all BackendClient methods to the selected delegate.
@override
Future<Map<String, dynamic>> listItems(
String location, int pageSize, int pageIndex, String subdirectory) =>
Expand Down Expand Up @@ -71,6 +91,14 @@ class BackendService implements OdysseyClient {
@override
Stream<Map<String, dynamic>> getStatusStream() => _delegate.getStatusStream();

@override
Future<List<Map<String, dynamic>>> getNotifications() =>
_delegate.getNotifications();

@override
Future<void> disableNotification(int timestamp) =>
_delegate.disableNotification(timestamp);

@override
Future<void> cancelPrint() => _delegate.cancelPrint();

Expand Down Expand Up @@ -115,4 +143,18 @@ class BackendService implements OdysseyClient {

@override
Future<void> displayTest(String test) => _delegate.displayTest(test);

@override
Future<Uint8List> getPlateLayerImage(int plateId, int layer) =>
_delegate.getPlateLayerImage(plateId, layer);

@override
Future<List<Map<String, dynamic>>> getAnalytics(int n) =>
_delegate.getAnalytics(n);

@override
Future<dynamic> getAnalyticValue(int id) => _delegate.getAnalyticValue(id);

@override
Future<dynamic> tareForceSensor() => _delegate.tareForceSensor();
}
39 changes: 39 additions & 0 deletions lib/backend_service/nanodlp/helpers/nano_analytics_config.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Mapping of NanoDLP analytic metric ids (T) to canonical keys used by the UI.
const List<Map<String, dynamic>> allChartConfig = [
{'key': 'LayerHeight', 'id': 0},
{'key': 'SolidArea', 'id': 1},
{'key': 'AreaCount', 'id': 2},
{'key': 'LargestArea', 'id': 3},
{'key': 'Speed', 'id': 4},
{'key': 'Cure', 'id': 5},
{'key': 'Pressure', 'id': 6},
{'key': 'TemperatureInside', 'id': 7},
{'key': 'TemperatureOutside', 'id': 8},
{'key': 'LayerTime', 'id': 9},
{'key': 'LiftHeight', 'id': 10},
{'key': 'TemperatureMCU', 'id': 11},
{'key': 'TemperatureInsideTarget', 'id': 12},
{'key': 'TemperatureOutsideTarget', 'id': 13},
{'key': 'TemperatureMCUTarget', 'id': 14},
{'key': 'MCUFanRPM', 'id': 15},
{'key': 'UVFanRPM', 'id': 16},
{'key': 'DynamicWait', 'id': 17},
{'key': 'TemperatureVat', 'id': 18},
{'key': 'TemperatureVatTarget', 'id': 19},
{'key': 'PTCFanRPM', 'id': 20},
{'key': 'AEGISFanRPM', 'id': 21},
{'key': 'TemperatureChamber', 'id': 22},
{'key': 'TemperatureChamberTarget', 'id': 23},
{'key': 'TemperaturePTC', 'id': 24},
{'key': 'TemperaturePTCTarget', 'id': 25},
{'key': 'VOCInlet', 'id': 26},
{'key': 'VOCOutlet', 'id': 27},
];

String? idToKey(int id) {
for (final e in allChartConfig) {
final val = e['id'];
if (val is int && val == id) return e['key'] as String?;
}
return null;
}
Loading