-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
323 additions
and
133 deletions.
There are no files selected for viewing
5 changes: 2 additions & 3 deletions
5
api/src/main/kotlin/nebulosa/api/autofocus/AutoFocusRequest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
56 changes: 56 additions & 0 deletions
56
api/src/main/kotlin/nebulosa/api/focusers/AbstractFocuserMoveTask.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package nebulosa.api.focusers | ||
|
||
import nebulosa.common.concurrency.cancel.CancellationListener | ||
import nebulosa.common.concurrency.cancel.CancellationSource | ||
import nebulosa.common.concurrency.cancel.CancellationToken | ||
import nebulosa.common.concurrency.latch.CountUpDownLatch | ||
import nebulosa.indi.device.focuser.FocuserEvent | ||
import nebulosa.indi.device.focuser.FocuserMoveFailed | ||
import nebulosa.indi.device.focuser.FocuserPositionChanged | ||
import nebulosa.log.loggerFor | ||
|
||
abstract class AbstractFocuserMoveTask : FocuserMoveTask, CancellationListener { | ||
|
||
@JvmField protected val latch = CountUpDownLatch() | ||
|
||
@Volatile private var initialPosition = 0 | ||
|
||
override fun handleFocuserEvent(event: FocuserEvent) { | ||
if (event.device === focuser) { | ||
when (event) { | ||
is FocuserPositionChanged -> if (focuser.position != initialPosition && !focuser.moving) latch.reset() | ||
is FocuserMoveFailed -> latch.reset() | ||
} | ||
} | ||
} | ||
|
||
protected abstract fun canMove(): Boolean | ||
|
||
protected abstract fun move() | ||
|
||
override fun execute(cancellationToken: CancellationToken) { | ||
if (!cancellationToken.isCancelled && focuser.connected && !focuser.moving && canMove()) { | ||
try { | ||
cancellationToken.listen(this) | ||
initialPosition = focuser.position | ||
LOG.info("Focuser move started. focuser={}", focuser) | ||
latch.countUp() | ||
move() | ||
latch.await() | ||
} finally { | ||
cancellationToken.unlisten(this) | ||
LOG.info("Focuser move finished. focuser={}", focuser) | ||
} | ||
} | ||
} | ||
|
||
override fun onCancel(source: CancellationSource) { | ||
focuser.abortFocus() | ||
latch.reset() | ||
} | ||
|
||
companion object { | ||
|
||
@JvmStatic private val LOG = loggerFor<AbstractFocuserMoveTask>() | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
api/src/main/kotlin/nebulosa/api/focusers/BacklashCompensation.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package nebulosa.api.focusers | ||
|
||
data class BacklashCompensation( | ||
@JvmField val mode: BacklashCompensationMode = BacklashCompensationMode.OVERSHOOT, | ||
@JvmField val backlashIn: Int = 0, | ||
@JvmField val backlashOut: Int = 0, | ||
) { | ||
|
||
companion object { | ||
|
||
@JvmStatic val EMPTY = BacklashCompensation() | ||
} | ||
} |
135 changes: 135 additions & 0 deletions
135
api/src/main/kotlin/nebulosa/api/focusers/BacklashCompensationFocuserMoveTask.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package nebulosa.api.focusers | ||
|
||
import nebulosa.common.concurrency.cancel.CancellationToken | ||
import nebulosa.indi.device.focuser.Focuser | ||
import nebulosa.indi.device.focuser.FocuserEvent | ||
import nebulosa.log.loggerFor | ||
|
||
/** | ||
* This decorator will wrap an absolute backlash [compensation] model around the [focuser]. | ||
* On each move an absolute backlash compensation value will be applied, if the focuser changes its moving direction | ||
* The returned position will then accommodate for this backlash and simulating the position without backlash. | ||
*/ | ||
data class BacklashCompensationFocuserMoveTask( | ||
override val focuser: Focuser, | ||
@JvmField @Volatile var position: Int, | ||
@JvmField val compensation: BacklashCompensation, | ||
) : FocuserMoveTask { | ||
|
||
enum class OvershootDirection { | ||
NONE, | ||
IN, | ||
OUT, | ||
} | ||
|
||
@Volatile private var offset = 0 | ||
@Volatile private var lastDirection = OvershootDirection.NONE | ||
|
||
private val task = FocuserMoveAbsoluteTask(focuser, 0) | ||
|
||
/** | ||
* Returns the adjusted position based on the amount of backlash compensation. | ||
*/ | ||
val adjustedPosition | ||
get() = focuser.position - offset | ||
|
||
override fun handleFocuserEvent(event: FocuserEvent) { | ||
task.handleFocuserEvent(event) | ||
} | ||
|
||
override fun execute(cancellationToken: CancellationToken) { | ||
if (!cancellationToken.isCancelled && focuser.connected && !focuser.moving) { | ||
val startPosition = focuser.position | ||
|
||
if (compensation.mode == BacklashCompensationMode.ABSOLUTE) { | ||
val adjustedTargetPosition = position + offset | ||
|
||
val finalizedTargetPosition = if (adjustedTargetPosition < 0) { | ||
offset = 0 | ||
0 | ||
} else if (adjustedTargetPosition > focuser.maxPosition) { | ||
offset = 0 | ||
focuser.maxPosition | ||
} else { | ||
val backlashCompensation = calculateAbsoluteBacklashCompensation(startPosition, adjustedTargetPosition) | ||
offset += backlashCompensation | ||
adjustedTargetPosition + backlashCompensation | ||
} | ||
|
||
moveFocuser(finalizedTargetPosition, cancellationToken) | ||
} else { | ||
val backlashCompensation = calculateOvershootBacklashCompensation(startPosition, position) | ||
|
||
if (backlashCompensation != 0) { | ||
val overshoot = position + backlashCompensation | ||
|
||
if (overshoot < 0) { | ||
LOG.info("overshooting position is below minimum 0, skipping overshoot") | ||
} else if (overshoot > focuser.maxPosition) { | ||
LOG.info("overshooting position is above maximum ${focuser.maxPosition}, skipping overshoot") | ||
} else { | ||
LOG.info("overshooting from $startPosition to overshoot position $overshoot using a compensation of $backlashCompensation") | ||
|
||
moveFocuser(overshoot, cancellationToken) | ||
|
||
LOG.info("moving back to position $position") | ||
} | ||
} | ||
|
||
moveFocuser(position, cancellationToken) | ||
} | ||
} | ||
} | ||
|
||
private fun moveFocuser(position: Int, cancellationToken: CancellationToken) { | ||
if (position > 0 && position <= focuser.maxPosition) { | ||
lastDirection = determineMovingDirection(focuser.position, position) | ||
task.position = position | ||
task.execute(cancellationToken) | ||
} | ||
} | ||
|
||
override fun reset() { | ||
task.reset() | ||
|
||
offset = 0 | ||
lastDirection = OvershootDirection.NONE | ||
} | ||
|
||
override fun close() { | ||
task.close() | ||
} | ||
|
||
private fun determineMovingDirection(prevPosition: Int, newPosition: Int): OvershootDirection { | ||
return if (newPosition > prevPosition) OvershootDirection.OUT | ||
else if (newPosition < prevPosition) OvershootDirection.IN | ||
else lastDirection | ||
} | ||
|
||
private fun calculateAbsoluteBacklashCompensation(lastPosition: Int, newPosition: Int): Int { | ||
val direction = determineMovingDirection(lastPosition, newPosition) | ||
|
||
return if (direction == OvershootDirection.IN && lastDirection == OvershootDirection.OUT) { | ||
LOG.info("Focuser is reversing direction from outwards to inwards") | ||
-compensation.backlashIn | ||
} else if (direction == OvershootDirection.OUT && lastDirection === OvershootDirection.IN) { | ||
LOG.info("Focuser is reversing direction from inwards to outwards") | ||
compensation.backlashOut | ||
} else { | ||
0 | ||
} | ||
} | ||
|
||
private fun calculateOvershootBacklashCompensation(lastPosition: Int, newPosition: Int): Int { | ||
val direction = determineMovingDirection(lastPosition, newPosition) | ||
|
||
return if (direction == OvershootDirection.IN && compensation.backlashIn != 0) -compensation.backlashIn | ||
else if (direction == OvershootDirection.OUT && compensation.backlashOut != 0) compensation.backlashOut | ||
else 0 | ||
} | ||
|
||
companion object { | ||
|
||
@JvmStatic private val LOG = loggerFor<BacklashCompensationFocuserMoveTask>() | ||
} | ||
} |
2 changes: 1 addition & 1 deletion
2
...api/autofocus/BacklashCompensationMode.kt → .../api/focusers/BacklashCompensationMode.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
package nebulosa.api.autofocus | ||
package nebulosa.api.focusers | ||
|
||
enum class BacklashCompensationMode { | ||
ABSOLUTE, | ||
|
59 changes: 7 additions & 52 deletions
59
api/src/main/kotlin/nebulosa/api/focusers/FocuserMoveAbsoluteTask.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,63 +1,18 @@ | ||
package nebulosa.api.focusers | ||
|
||
import nebulosa.common.concurrency.cancel.CancellationListener | ||
import nebulosa.common.concurrency.cancel.CancellationSource | ||
import nebulosa.common.concurrency.cancel.CancellationToken | ||
import nebulosa.common.concurrency.latch.CountUpDownLatch | ||
import nebulosa.indi.device.focuser.Focuser | ||
import nebulosa.indi.device.focuser.FocuserEvent | ||
import nebulosa.indi.device.focuser.FocuserMoveFailed | ||
import nebulosa.indi.device.focuser.FocuserPositionChanged | ||
import nebulosa.log.loggerFor | ||
import kotlin.math.abs | ||
|
||
data class FocuserMoveAbsoluteTask( | ||
override val focuser: Focuser, | ||
@JvmField val position: Int, | ||
) : FocuserMoveTask, CancellationListener { | ||
@JvmField @Volatile var position: Int, | ||
) : AbstractFocuserMoveTask() { | ||
|
||
private val latch = CountUpDownLatch() | ||
override fun canMove() = position != focuser.position && position > 0 && position < focuser.maxPosition | ||
|
||
override fun handleFocuserEvent(event: FocuserEvent) { | ||
if (event.device === focuser) { | ||
when (event) { | ||
is FocuserPositionChanged -> if (focuser.position == position) latch.reset() | ||
is FocuserMoveFailed -> latch.reset() | ||
} | ||
} | ||
} | ||
|
||
override fun execute(cancellationToken: CancellationToken) { | ||
if (!cancellationToken.isCancelled && focuser.connected | ||
&& !focuser.moving && position != focuser.position | ||
) { | ||
try { | ||
cancellationToken.listen(this) | ||
|
||
LOG.info("Focuser move started. position={}, focuser={}", position, focuser) | ||
|
||
latch.countUp() | ||
|
||
if (focuser.canAbsoluteMove) focuser.moveFocusTo(position) | ||
else if (focuser.position - position < 0) focuser.moveFocusIn(abs(focuser.position - position)) | ||
else focuser.moveFocusOut(abs(focuser.position - position)) | ||
|
||
latch.await() | ||
} finally { | ||
cancellationToken.unlisten(this) | ||
} | ||
|
||
LOG.info("Focuser move finished. position={}, focuser={}", position, focuser) | ||
} | ||
} | ||
|
||
override fun onCancel(source: CancellationSource) { | ||
focuser.abortFocus() | ||
latch.reset() | ||
} | ||
|
||
companion object { | ||
|
||
@JvmStatic private val LOG = loggerFor<FocuserMoveAbsoluteTask>() | ||
override fun move() { | ||
if (focuser.canAbsoluteMove) focuser.moveFocusTo(position) | ||
else if (position < focuser.position) focuser.moveFocusIn(abs(position - focuser.position)) | ||
else focuser.moveFocusOut(abs(position - focuser.position)) | ||
} | ||
} |
Oops, something went wrong.