ironOS native ios app
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add a demo mode

+158
+137
ios/Tinkcil/BLEManager.swift
··· 20 20 var firmwareVersion: String = "" 21 21 var buildID: String = "" 22 22 var deviceSerial: String = "" 23 + var isDemoMode = false 23 24 24 25 // Temperature history for graph (circular buffer) 25 26 var temperatureHistory = CircularBuffer<TemperaturePoint>(capacity: 60) ··· 43 44 private var pendingWrites: [CBUUID: UInt16] = [:] 44 45 private var settingReadCompletions: [CBUUID: (UInt16?) -> Void] = [:] 45 46 private var operationTimeouts: [CBUUID: DispatchWorkItem] = [:] 47 + private var demoTimer: Timer? 46 48 47 49 // MARK: - Init 48 50 ··· 116 118 } 117 119 118 120 func disconnect() { 121 + if isDemoMode { 122 + stopDemoMode() 123 + return 124 + } 125 + 119 126 stopPolling() 120 127 121 128 if let peripheral = connectedPeripheral { ··· 158 165 159 166 @MainActor 160 167 func writeSetting(index: UInt16, value: UInt16) { 168 + if isDemoMode { 169 + settingsCache.set(value, for: index) 170 + if index == 0 { 171 + liveData.setpoint = UInt32(value) 172 + } 173 + return 174 + } 175 + 161 176 guard connectionState == .connected, 162 177 let peripheral = connectedPeripheral else { 163 178 lastError = .notConnected ··· 206 221 return 207 222 } 208 223 224 + if isDemoMode { 225 + completion(nil) 226 + return 227 + } 228 + 209 229 guard connectionState == .connected, 210 230 let peripheral = connectedPeripheral else { 211 231 lastError = .notConnected ··· 248 268 249 269 @MainActor 250 270 func saveSettings() { 271 + if isDemoMode { return } 272 + 251 273 guard connectionState == .connected, 252 274 let peripheral = connectedPeripheral, 253 275 let characteristic = discoveredCharacteristics[IronOSUUIDs.saveSettings] else { ··· 262 284 } 263 285 264 286 func setSlowPolling() { 287 + if isDemoMode { return } 265 288 updatePollingInterval(0.2) 266 289 } 267 290 268 291 func setFastPolling() { 292 + if isDemoMode { return } 269 293 guard connectionState == .connected else { return } 270 294 updatePollingInterval(0.1) 295 + } 296 + 297 + // MARK: - Demo Mode 298 + 299 + @MainActor 300 + func startDemoMode() { 301 + isDemoMode = true 302 + connectionState = .connected 303 + 304 + deviceName = "Pinecil-DEMO" 305 + firmwareVersion = "v2.22" 306 + buildID = "v2.22" 307 + deviceSerial = "DEMO000000000000" 308 + 309 + liveData.liveTemp = 25 310 + liveData.setpoint = 320 311 + liveData.dcInput = 200 // 20.0V 312 + liveData.handleTemp = 250 // 25.0°C 313 + liveData.powerLevel = 0 314 + liveData.powerSource = 2 // PD 315 + liveData.tipResistance = 620 // 6.20 Ω 316 + liveData.uptime = 0 317 + liveData.lastMovement = 0 318 + liveData.maxTemp = 450 319 + liveData.rawTip = 0 320 + liveData.hallSensor = 0 321 + liveData.operatingMode = 1 // soldering 322 + liveData.estimatedWatts = 0 323 + 324 + // Populate settings cache with realistic defaults 325 + let defaults: [(UInt16, UInt16)] = [ 326 + (0, 320), // Soldering temp 327 + (1, 150), // Sleep temp 328 + (2, 1), // Sleep time (min) 329 + (6, 2), // Orientation (auto) 330 + (7, 6), // Motion sensitivity 331 + (11, 10), // Shutdown time (min) 332 + (13, 0), // Detailed idle screen (off) 333 + (14, 0), // Detailed soldering screen (off) 334 + (17, 0), // Locking mode (off) 335 + (22, 420), // Boost temp 336 + (24, 65), // Power limit (W) 337 + (25, 0), // Reverse buttons (off) 338 + (26, 10), // Long press step 339 + (27, 1), // Short press step 340 + (28, 7), // Hall sensitivity 341 + (33, 0), // Invert display (off) 342 + (34, 51), // Brightness 343 + ] 344 + for (index, value) in defaults { 345 + settingsCache.set(value, for: index) 346 + } 347 + 348 + temperatureHistory.clear() 349 + 350 + demoTimer?.invalidate() 351 + demoTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in 352 + guard let self else { return } 353 + Task { @MainActor in 354 + self.demoTick() 355 + } 356 + } 357 + if let timer = demoTimer { 358 + RunLoop.main.add(timer, forMode: .common) 359 + } 360 + } 361 + 362 + @MainActor 363 + private func demoTick() { 364 + let current = Int(liveData.liveTemp) 365 + let target = Int(liveData.setpoint) 366 + let delta = target - current 367 + let noise = Int.random(in: -1...1) 368 + 369 + if abs(delta) <= 2 { 370 + // At setpoint — hold steady with small fluctuation 371 + liveData.liveTemp = UInt32(clamping: target + noise) 372 + liveData.estimatedWatts = UInt32.random(in: 20...40) // ~2-4W maintenance 373 + liveData.powerLevel = UInt32.random(in: 5...20) 374 + liveData.operatingMode = 1 // soldering 375 + } else if delta > 0 { 376 + // Heating up 377 + let step = min(max(delta / 8, 3), 5) 378 + liveData.liveTemp = UInt32(clamping: current + step + noise) 379 + liveData.estimatedWatts = UInt32(clamping: 500 + Int.random(in: -30...30)) // ~50W 380 + liveData.powerLevel = UInt32(clamping: 200 + Int.random(in: -20...20)) 381 + liveData.operatingMode = 1 382 + } else { 383 + // Cooling down 384 + let step = min(max(-delta / 10, 2), 4) 385 + liveData.liveTemp = UInt32(clamping: current - step + noise) 386 + liveData.estimatedWatts = 0 387 + liveData.powerLevel = 0 388 + liveData.operatingMode = 1 389 + } 390 + 391 + liveData.uptime += 1 392 + liveData.lastMovement = UInt32.random(in: 0...10) 393 + 394 + recordTemperature() 395 + } 396 + 397 + private func stopDemoMode() { 398 + demoTimer?.invalidate() 399 + demoTimer = nil 400 + isDemoMode = false 401 + liveData = IronOSLiveData() 402 + deviceName = "" 403 + firmwareVersion = "" 404 + buildID = "" 405 + deviceSerial = "" 406 + temperatureHistory.clear() 407 + connectionState = .disconnected 271 408 } 272 409 273 410 private func updatePollingInterval(_ interval: TimeInterval) {
+9
ios/Tinkcil/ContentView.swift
··· 390 390 .buttonStyle(.borderedProminent) 391 391 .accessibilityLabel("Scan for device") 392 392 .accessibilityHint("Searches for nearby soldering iron") 393 + 394 + Button("Try Demo") { 395 + hapticLight() 396 + bleManager.startDemoMode() 397 + } 398 + .font(.subheadline) 399 + .foregroundStyle(.secondary) 400 + .accessibilityLabel("Try demo mode") 401 + .accessibilityHint("Experience the app with simulated soldering iron data") 393 402 } 394 403 } 395 404 .padding(32)
+12
ios/Tinkcil/Localizable.xcstrings
··· 2190 2190 } 2191 2191 } 2192 2192 }, 2193 + "Experience the app with simulated soldering iron data" : { 2194 + "comment" : "A button that, when tapped, allows users to experience the app with simulated soldering iron data.", 2195 + "isCommentAutoGenerated" : true 2196 + }, 2193 2197 "Heating to %u degrees" : { 2194 2198 "comment" : "A hint that appears when the user taps on the temperature display, indicating whether the heating system is active and if so, at what temperature.", 2195 2199 "isCommentAutoGenerated" : true, ··· 6591 6595 } 6592 6596 } 6593 6597 } 6598 + }, 6599 + "Try Demo" : { 6600 + "comment" : "A button that allows users to experience the app with simulated soldering iron data.", 6601 + "isCommentAutoGenerated" : true 6602 + }, 6603 + "Try demo mode" : { 6604 + "comment" : "A button that, when tapped, transitions the app to a demo mode using simulated data.", 6605 + "isCommentAutoGenerated" : true 6594 6606 }, 6595 6607 "Value" : { 6596 6608 "comment" : "Label for the y-axis in the temperature graph.",