Compare commits

..

53 Commits

Author SHA1 Message Date
01261272b4 . 2026-03-11 17:18:21 +03:00
e61146db69 fixed stupid bug 2026-03-11 12:36:45 +03:00
2eb7b214b2 add more errors to model 2026-03-11 11:03:10 +03:00
27b1cecc7b enc 2026-03-11 10:05:42 +03:00
9e84306a94 poll test works @high speed 2026-02-09 22:00:51 +03:00
c0c36388db test of polling sensors 2026-01-26 22:25:20 +03:00
ed24d0b9e2 add records to AsibFM700 config file according to Eddy's commit 2026-01-26 18:23:03 +03:00
bccc7a9b29 1st working approach after last changes 2026-01-22 21:18:01 +03:00
873f292a11 fixed race bug 2026-01-21 23:20:58 +03:00
f7cb279841 change macros to config parameters 2026-01-20 23:18:36 +03:00
fd96dc395b ... 2026-01-16 12:23:58 +03:00
Timur A. Fatkhullin
0aa0113be3 ... 2026-01-15 23:21:28 +03:00
01d5657b1b ... 2026-01-15 19:11:16 +03:00
09cf5f9c19 ... 2026-01-14 19:15:18 +03:00
66c3c7e6c7 ... 2025-12-29 16:11:23 +03:00
fd2776084a ... 2025-12-25 16:18:54 +03:00
Timur A. Fatkhullin
bbc35b02bb ... 2025-12-22 23:02:41 +03:00
Timur A. Fatkhullin
8ce6ffc41c ... 2025-12-22 22:56:49 +03:00
54d6c25171 ... 2025-12-22 17:13:04 +03:00
2c7d563994 ... 2025-12-19 12:01:36 +03:00
bc12777f18 ... 2025-12-18 18:21:17 +03:00
7948321cce ... 2025-12-17 17:25:00 +03:00
b12c0ec521 ... 2025-12-16 17:53:50 +03:00
Timur A. Fatkhullin
d417c03f59 ... 2025-12-16 02:22:03 +03:00
a8dd511366 ... 2025-12-15 17:26:26 +03:00
a30729fb37 ... 2025-12-14 05:32:04 +03:00
a70339c55e ... 2025-12-14 00:25:03 +03:00
6fca94e571 ... 2025-12-12 17:21:11 +03:00
255c34dbb2 ... 2025-12-11 17:40:23 +03:00
3c0c719e37 ... 2025-12-10 17:24:00 +03:00
Timur A. Fatkhullin
0ddd633bc9 ... 2025-12-10 00:18:55 +03:00
28ecf307a8 add saving slewing trajectory in a file 2025-12-09 16:55:46 +03:00
57467ce48f ... 2025-12-08 17:34:59 +03:00
196ed3be1b change time from double to struct timespec 2025-12-08 13:31:23 +03:00
acd26edc9c add PID-related items in mount config
rewrite AsibFM700ServoController methods according to new time point
representation in LibSidServo
2025-12-04 18:11:41 +03:00
da9cd51e5c timespec 2025-12-04 11:10:27 +03:00
d868810048 cmake fixes 2025-12-03 18:14:34 +03:00
Timur A. Fatkhullin
7c9612c3a2 MccSimpleSlewingModel: the code has been rewritten in accordance with the
changes in Eddy's LibSidServo
2025-12-03 00:35:52 +03:00
54419b8e60 ... 2025-12-02 18:05:08 +03:00
7dfb0d5e9b ... 2025-12-02 18:02:57 +03:00
bbf7314592 . 2025-12-02 12:33:16 +03:00
6dde28e8d9 some fixes 2025-12-01 17:28:18 +03:00
9066b3f091 ... 2025-12-01 17:18:04 +03:00
c514d4adcc Merge branch 'main' of ssh://95.140.147.151:/home/git/mountcontrol 2025-12-01 10:07:45 +03:00
cca58e8ba9 ... 2025-12-01 02:49:01 +03:00
bf55a45cf9 ... 2025-11-27 18:01:40 +03:00
a825a6935b MccGenericNetworkServer: fix client session thread pool behavior in
destructor
2025-11-27 09:20:42 +03:00
43638f383f ran client sessions in separated thread pool 2025-11-26 18:01:34 +03:00
a42f6dbc98 ... 2025-11-25 08:49:43 +03:00
Timur A. Fatkhullin
acced75fa2 ... 2025-11-24 21:52:22 +03:00
e548451617 ... 2025-11-24 18:00:46 +03:00
e529265a63 ... 2025-11-21 12:33:49 +03:00
b2c27a6f5c ... 2025-11-21 02:02:35 +03:00
57 changed files with 5995 additions and 1468 deletions

View File

@@ -13,9 +13,9 @@ set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
# ******* C++ PART OF THE PROJECT ******* # ******* C++ PART OF THE PROJECT *******
set(EXAMPLES OFF CACHE BOOL "" FORCE) set(EXAMPLES OFF CACHE BOOL "" FORCE)
set(CMAKE_BUILD_TYPE "Release") # set(CMAKE_BUILD_TYPE "Release")
add_subdirectory(LibSidServo)
set(CMAKE_BUILD_TYPE "Debug") set(CMAKE_BUILD_TYPE "Debug")
add_subdirectory(LibSidServo)
# add_subdirectory(cxx) # add_subdirectory(cxx)
add_subdirectory(mcc) add_subdirectory(mcc)

View File

@@ -0,0 +1,218 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 18.0.0, 2026-03-11T12:36:26. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
<value type="QByteArray">{cf63021e-ef53-49b0-b03b-2f2570cdf3b6}</value>
</data>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
<value type="qlonglong">0</value>
</data>
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoDetect">true</value>
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
<value type="QString" key="language">Cpp</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
</valuemap>
</valuemap>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
<value type="QString" key="language">QmlJS</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
</valuemap>
</valuemap>
<value type="qlonglong" key="EditorConfiguration.CodeStyle.Count">2</value>
<value type="QByteArray" key="EditorConfiguration.Codec">KOI8-R</value>
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
<value type="int" key="EditorConfiguration.IndentSize">4</value>
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
<value type="int" key="EditorConfiguration.LineEndingBehavior">0</value>
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
<value type="int" key="EditorConfiguration.PreferAfterWhitespaceComments">0</value>
<value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</value>
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">false</value>
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">1</value>
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
<value type="int" key="EditorConfiguration.TabSize">8</value>
<value type="bool" key="EditorConfiguration.UseGlobal">true</value>
<value type="bool" key="EditorConfiguration.UseIndenter">false</value>
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
<value type="bool" key="EditorConfiguration.cleanIndentation">true</value>
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
<value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</value>
<value type="bool" key="EditorConfiguration.inEntireDocument">true</value>
<value type="bool" key="EditorConfiguration.skipTrailingWhitespace">true</value>
<value type="bool" key="EditorConfiguration.tintMarginArea">true</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.PluginSettings</variable>
<valuemap type="QVariantMap">
<valuemap type="QVariantMap" key="AutoTest.ActiveFrameworks">
<value type="bool" key="AutoTest.Framework.Boost">true</value>
<value type="bool" key="AutoTest.Framework.CTest">false</value>
<value type="bool" key="AutoTest.Framework.Catch">true</value>
<value type="bool" key="AutoTest.Framework.GTest">true</value>
<value type="bool" key="AutoTest.Framework.QtQuickTest">true</value>
<value type="bool" key="AutoTest.Framework.QtTest">true</value>
</valuemap>
<value type="bool" key="AutoTest.ApplyFilter">false</value>
<valuemap type="QVariantMap" key="AutoTest.CheckStates"/>
<valuelist type="QVariantList" key="AutoTest.PathFilters"/>
<value type="int" key="AutoTest.RunAfterBuild">0</value>
<value type="bool" key="AutoTest.UseGlobal">true</value>
<valuemap type="QVariantMap" key="ClangTools">
<value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
<value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
<value type="QString" key="ClangTools.DiagnosticConfig">Builtin.DefaultTidyAndClazy</value>
<value type="int" key="ClangTools.ParallelJobs">4</value>
<value type="bool" key="ClangTools.PreferConfigFile">true</value>
<valuelist type="QVariantList" key="ClangTools.SelectedDirs"/>
<valuelist type="QVariantList" key="ClangTools.SelectedFiles"/>
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
<value type="bool" key="ClangTools.UseGlobalSettings">true</value>
</valuemap>
<value type="int" key="RcSync">0</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap">
<value type="QString" key="DeviceType">Desktop</value>
<value type="bool" key="HasPerBcDcs">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{91347f2c-5221-46a7-80b1-0a054ca02f79}</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/eddy/Docs/SAO/10micron/C-sources/erfa_functions</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
<value type="QString">all</value>
</valuelist>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
<value type="QString">clean</value>
</valuelist>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">По умолчанию</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericBuildConfiguration</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<value type="QList&lt;int&gt;" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<value type="QList&lt;int&gt;" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.TargetCount</variable>
<value type="qlonglong">1</value>
</data>
<data>
<variable>Version</variable>
<value type="int">22</value>
</data>
</qtcreator>

View File

@@ -0,0 +1,218 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 18.0.0, 2025-11-27T17:22:09. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
<value type="QByteArray">{cf63021e-ef53-49b0-b03b-2f2570cdf3b6}</value>
</data>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
<value type="qlonglong">0</value>
</data>
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoDetect">true</value>
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
<value type="QString" key="language">Cpp</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
</valuemap>
</valuemap>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
<value type="QString" key="language">QmlJS</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
</valuemap>
</valuemap>
<value type="qlonglong" key="EditorConfiguration.CodeStyle.Count">2</value>
<value type="QByteArray" key="EditorConfiguration.Codec">KOI8-R</value>
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
<value type="int" key="EditorConfiguration.IndentSize">4</value>
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
<value type="int" key="EditorConfiguration.LineEndingBehavior">0</value>
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
<value type="int" key="EditorConfiguration.PreferAfterWhitespaceComments">0</value>
<value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</value>
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">false</value>
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">1</value>
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
<value type="int" key="EditorConfiguration.TabSize">8</value>
<value type="bool" key="EditorConfiguration.UseGlobal">true</value>
<value type="bool" key="EditorConfiguration.UseIndenter">false</value>
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
<value type="bool" key="EditorConfiguration.cleanIndentation">true</value>
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
<value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</value>
<value type="bool" key="EditorConfiguration.inEntireDocument">true</value>
<value type="bool" key="EditorConfiguration.skipTrailingWhitespace">true</value>
<value type="bool" key="EditorConfiguration.tintMarginArea">true</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.PluginSettings</variable>
<valuemap type="QVariantMap">
<valuemap type="QVariantMap" key="AutoTest.ActiveFrameworks">
<value type="bool" key="AutoTest.Framework.Boost">true</value>
<value type="bool" key="AutoTest.Framework.CTest">false</value>
<value type="bool" key="AutoTest.Framework.Catch">true</value>
<value type="bool" key="AutoTest.Framework.GTest">true</value>
<value type="bool" key="AutoTest.Framework.QtQuickTest">true</value>
<value type="bool" key="AutoTest.Framework.QtTest">true</value>
</valuemap>
<value type="bool" key="AutoTest.ApplyFilter">false</value>
<valuemap type="QVariantMap" key="AutoTest.CheckStates"/>
<valuelist type="QVariantList" key="AutoTest.PathFilters"/>
<value type="int" key="AutoTest.RunAfterBuild">0</value>
<value type="bool" key="AutoTest.UseGlobal">true</value>
<valuemap type="QVariantMap" key="ClangTools">
<value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
<value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
<value type="QString" key="ClangTools.DiagnosticConfig">Builtin.DefaultTidyAndClazy</value>
<value type="int" key="ClangTools.ParallelJobs">4</value>
<value type="bool" key="ClangTools.PreferConfigFile">true</value>
<valuelist type="QVariantList" key="ClangTools.SelectedDirs"/>
<valuelist type="QVariantList" key="ClangTools.SelectedFiles"/>
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
<value type="bool" key="ClangTools.UseGlobalSettings">true</value>
</valuemap>
<value type="int" key="RcSync">0</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap">
<value type="QString" key="DeviceType">Desktop</value>
<value type="bool" key="HasPerBcDcs">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{91347f2c-5221-46a7-80b1-0a054ca02f79}</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/eddy/Docs/SAO/10micron/C-sources/erfa_functions</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
<value type="QString">all</value>
</valuelist>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
<value type="QString">clean</value>
</valuelist>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">По умолчанию</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericBuildConfiguration</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<value type="QList&lt;int&gt;" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<value type="QList&lt;int&gt;" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.TargetCount</variable>
<value type="qlonglong">1</value>
</data>
<data>
<variable>Version</variable>
<value type="int">22</value>
</data>
</qtcreator>

View File

@@ -84,49 +84,51 @@ typedef struct{
* @return calculated new speed or -1 for max speed * @return calculated new speed or -1 for max speed
*/ */
static double getspeed(const coordval_t *tagpos, PIDpair_t *pidpair, axisdata_t *axis){ static double getspeed(const coordval_t *tagpos, PIDpair_t *pidpair, axisdata_t *axis){
if(tagpos->t < axis->position.t || tagpos->t - axis->position.t > MCC_PID_MAX_DT){ double dt = timediff(&tagpos->t, &axis->position.t);
DBG("target time: %g, axis time: %g - too big! (%g)", tagpos->t, axis->position.t, MCC_PID_MAX_DT); if(dt < 0 || dt > Conf.PIDMaxDt){
DBG("target time: %ld, axis time: %ld - too big! (tag-ax=%g)", tagpos->t.tv_sec, axis->position.t.tv_sec, dt);
return axis->speed.val; // data is too old or wrong return axis->speed.val; // data is too old or wrong
} }
double error = tagpos->val - axis->position.val, fe = fabs(error); double error = tagpos->val - axis->position.val, fe = fabs(error);
DBG("error: %g", error);
PIDController_t *pid = NULL; PIDController_t *pid = NULL;
switch(axis->state){ switch(axis->state){
case AXIS_SLEWING: case AXIS_SLEWING:
if(fe < MCC_MAX_POINTING_ERR){ if(fe < Conf.MaxPointingErr){
axis->state = AXIS_POINTING; axis->state = AXIS_POINTING;
DBG("--> Pointing"); DBG("--> Pointing");
pid = pidpair->PIDC; pid = pidpair->PIDC;
}else{ }else{
DBG("Slewing..."); DBG("Slewing...");
return -1.; // max speed for given axis return NAN; // max speed for given axis
} }
break; break;
case AXIS_POINTING: case AXIS_POINTING:
if(fe < MCC_MAX_GUIDING_ERR){ if(fe < Conf.MaxFinePointingErr){
axis->state = AXIS_GUIDING; axis->state = AXIS_GUIDING;
DBG("--> Guiding"); DBG("--> Guiding");
pid = pidpair->PIDV; pid = pidpair->PIDV;
}else if(fe > MCC_MAX_POINTING_ERR){ }else if(fe > Conf.MaxPointingErr){
DBG("--> Slewing"); DBG("--> Slewing");
axis->state = AXIS_SLEWING; axis->state = AXIS_SLEWING;
return -1.; return NAN;
} else pid = pidpair->PIDC; } else pid = pidpair->PIDC;
break; break;
case AXIS_GUIDING: case AXIS_GUIDING:
pid = pidpair->PIDV; pid = pidpair->PIDV;
if(fe > MCC_MAX_GUIDING_ERR){ if(fe > Conf.MaxFinePointingErr){
DBG("--> Pointing"); DBG("--> Pointing");
axis->state = AXIS_POINTING; axis->state = AXIS_POINTING;
pid = pidpair->PIDC; pid = pidpair->PIDC;
}else if(fe < MCC_MAX_ATTARGET_ERR){ }else if(fe < Conf.MaxGuidingErr){
DBG("At target"); DBG("At target");
// TODO: we can point somehow that we are at target or introduce new axis state // TODO: we can point somehow that we are at target or introduce new axis state
}else DBG("Current error: %g", fe); }else DBG("Current error: %g", fe);
break; break;
case AXIS_STOPPED: // start pointing to target; will change speed next time case AXIS_STOPPED: // start pointing to target; will change speed next time
DBG("AXIS STOPPED!!!!"); DBG("AXIS STOPPED!!!! --> Slewing");
axis->state = AXIS_SLEWING; axis->state = AXIS_SLEWING;
return -1.; return getspeed(tagpos, pidpair, axis);
case AXIS_ERROR: case AXIS_ERROR:
DBG("Can't move from erroneous state"); DBG("Can't move from erroneous state");
return 0.; return 0.;
@@ -135,16 +137,16 @@ static double getspeed(const coordval_t *tagpos, PIDpair_t *pidpair, axisdata_t
DBG("WTF? Where is a PID?"); DBG("WTF? Where is a PID?");
return axis->speed.val; return axis->speed.val;
} }
if(tagpos->t < pid->prevT || tagpos->t - pid->prevT > MCC_PID_MAX_DT){ double dtpid = timediff(&tagpos->t, &pid->prevT);
if(dtpid < 0 || dtpid > Conf.PIDMaxDt){
DBG("time diff too big: clear PID"); DBG("time diff too big: clear PID");
pid_clear(pid); pid_clear(pid);
} }
double dt = tagpos->t - pid->prevT; if(dtpid > Conf.PIDMaxDt) dtpid = Conf.PIDCycleDt;
if(dt > MCC_PID_MAX_DT) dt = MCC_PID_CYCLE_TIME;
pid->prevT = tagpos->t; pid->prevT = tagpos->t;
DBG("CALC PID (er=%g, dt=%g), state=%d", error, dt, axis->state); DBG("CALC PID (er=%g, dt=%g), state=%d", error, dtpid, axis->state);
double tagspeed = pid_calculate(pid, error, dt); double tagspeed = pid_calculate(pid, error, dtpid);
if(axis->state == AXIS_GUIDING) return axis->speed.val + tagspeed / dt; // velocity-based if(axis->state == AXIS_GUIDING) return axis->speed.val + tagspeed / dtpid; // velocity-based
return tagspeed; // coordinate-based return tagspeed; // coordinate-based
} }
@@ -154,22 +156,23 @@ static double getspeed(const coordval_t *tagpos, PIDpair_t *pidpair, axisdata_t
* @param endpoint - stop point (some far enough point to stop in case of hang) * @param endpoint - stop point (some far enough point to stop in case of hang)
* @return error code * @return error code
*/ */
mcc_errcodes_t correct2(const coordval_pair_t *target, const coordpair_t *endpoint){ mcc_errcodes_t correct2(const coordval_pair_t *target){
static PIDpair_t pidX = {0}, pidY = {0}; static PIDpair_t pidX = {0}, pidY = {0};
if(!pidX.PIDC){ if(!pidX.PIDC){
pidX.PIDC = pid_create(&Conf.XPIDC, MCC_PID_CYCLE_TIME / MCC_PID_REFRESH_DT); pidX.PIDC = pid_create(&Conf.XPIDC, Conf.PIDCycleDt / Conf.PIDRefreshDt);
if(!pidX.PIDC) return MCC_E_FATAL; if(!pidX.PIDC) return MCC_E_FATAL;
pidX.PIDV = pid_create(&Conf.XPIDV, MCC_PID_CYCLE_TIME / MCC_PID_REFRESH_DT); pidX.PIDV = pid_create(&Conf.XPIDV, Conf.PIDCycleDt / Conf.PIDRefreshDt);
if(!pidX.PIDV) return MCC_E_FATAL; if(!pidX.PIDV) return MCC_E_FATAL;
} }
if(!pidY.PIDC){ if(!pidY.PIDC){
pidY.PIDC = pid_create(&Conf.YPIDC, MCC_PID_CYCLE_TIME / MCC_PID_REFRESH_DT); pidY.PIDC = pid_create(&Conf.YPIDC, Conf.PIDCycleDt / Conf.PIDRefreshDt);
if(!pidY.PIDC) return MCC_E_FATAL; if(!pidY.PIDC) return MCC_E_FATAL;
pidY.PIDV = pid_create(&Conf.YPIDV, MCC_PID_CYCLE_TIME / MCC_PID_REFRESH_DT); pidY.PIDV = pid_create(&Conf.YPIDV, Conf.PIDCycleDt / Conf.PIDRefreshDt);
if(!pidY.PIDV) return MCC_E_FATAL; if(!pidY.PIDV) return MCC_E_FATAL;
} }
mountdata_t m; mountdata_t m;
coordpair_t tagspeed; coordpair_t tagspeed; // absolute value of speed
double Xsign = 1., Ysign = 1.; // signs of speed (for target calculation)
if(MCC_E_OK != Mount.getMountData(&m)) return MCC_E_FAILED; if(MCC_E_OK != Mount.getMountData(&m)) return MCC_E_FAILED;
axisdata_t axis; axisdata_t axis;
DBG("state: %d/%d", m.Xstate, m.Ystate); DBG("state: %d/%d", m.Xstate, m.Ystate);
@@ -177,20 +180,42 @@ mcc_errcodes_t correct2(const coordval_pair_t *target, const coordpair_t *endpoi
axis.position = m.encXposition; axis.position = m.encXposition;
axis.speed = m.encXspeed; axis.speed = m.encXspeed;
tagspeed.X = getspeed(&target->X, &pidX, &axis); tagspeed.X = getspeed(&target->X, &pidX, &axis);
if(tagspeed.X < 0.) tagspeed.X = -tagspeed.X; if(isnan(tagspeed.X)){ // max speed
if(tagspeed.X > MCC_MAX_X_SPEED) tagspeed.X = MCC_MAX_X_SPEED; if(target->X.val < axis.position.val) Xsign = -1.;
tagspeed.X = Xlimits.max.speed;
}else{
if(tagspeed.X < 0.){ tagspeed.X = -tagspeed.X; Xsign = -1.; }
if(tagspeed.X > Xlimits.max.speed) tagspeed.X = Xlimits.max.speed;
}
axis_status_t xstate = axis.state; axis_status_t xstate = axis.state;
axis.state = m.Ystate; axis.state = m.Ystate;
axis.position = m.encYposition; axis.position = m.encYposition;
axis.speed = m.encYspeed; axis.speed = m.encYspeed;
tagspeed.Y = getspeed(&target->Y, &pidY, &axis); tagspeed.Y = getspeed(&target->Y, &pidY, &axis);
if(tagspeed.Y < 0.) tagspeed.Y = -tagspeed.Y; if(isnan(tagspeed.Y)){ // max speed
if(tagspeed.Y > MCC_MAX_Y_SPEED) tagspeed.Y = MCC_MAX_Y_SPEED; if(target->Y.val < axis.position.val) Ysign = -1.;
tagspeed.Y = Ylimits.max.speed;
}else{
if(tagspeed.Y < 0.){ tagspeed.Y = -tagspeed.Y; Ysign = -1.; }
if(tagspeed.Y > Ylimits.max.speed) tagspeed.Y = Ylimits.max.speed;
}
axis_status_t ystate = axis.state; axis_status_t ystate = axis.state;
if(m.Xstate != xstate || m.Ystate != ystate){ if(m.Xstate != xstate || m.Ystate != ystate){
DBG("State changed"); DBG("State changed");
setStat(xstate, ystate); setStat(xstate, ystate);
} }
DBG("TAG speeds: %g/%g", tagspeed.X, tagspeed.Y); coordpair_t endpoint;
return Mount.moveWspeed(endpoint, &tagspeed); // allow at least PIDMaxDt moving with target speed
double dv = fabs(tagspeed.X - m.encXspeed.val);
double adder = dv/Xlimits.max.accel * (m.encXspeed.val + dv / 2.) // distanse with changing speed
+ Conf.PIDMaxDt * tagspeed.X // PIDMaxDt const speed moving
+ tagspeed.X * tagspeed.X / Xlimits.max.accel / 2.; // stopping
endpoint.X = m.encXposition.val + Xsign * adder;
dv = fabs(tagspeed.Y - m.encYspeed.val);
adder = dv/Ylimits.max.accel * (m.encYspeed.val + dv / 2.)
+ Conf.PIDMaxDt * tagspeed.Y
+ tagspeed.Y * tagspeed.Y / Ylimits.max.accel / 2.;
endpoint.Y = m.encYposition.val + Ysign * adder;
DBG("TAG speeds: %g/%g (deg/s); TAG pos: %g/%g (deg)", tagspeed.X/M_PI*180., tagspeed.Y/M_PI*180., endpoint.X/M_PI*180., endpoint.Y/M_PI*180.);
return Mount.moveWspeed(&endpoint, &tagspeed);
} }

View File

@@ -27,7 +27,7 @@ typedef struct {
double prev_error; // Previous error double prev_error; // Previous error
double integral; // Integral term double integral; // Integral term
double *pidIarray; // array for Integral double *pidIarray; // array for Integral
double prevT; // time of previous correction struct timespec prevT; // time of previous correction
size_t pidIarrSize; // it's size size_t pidIarrSize; // it's size
size_t curIidx; // and index of current element size_t curIidx; // and index of current element
} PIDController_t; } PIDController_t;
@@ -37,4 +37,4 @@ void pid_clear(PIDController_t *pid);
void pid_delete(PIDController_t **pid); void pid_delete(PIDController_t **pid);
double pid_calculate(PIDController_t *pid, double error, double dt); double pid_calculate(PIDController_t *pid, double error, double dt);
mcc_errcodes_t correct2(const coordval_pair_t *target, const coordpair_t *endpoint); mcc_errcodes_t correct2(const coordval_pair_t *target);

3
LibSidServo/TODO Normal file
View File

@@ -0,0 +1,3 @@
fix encoders opening for several tries
encoderthread2() - change main cycle (remove pause, read data independently, ask for new only after timeout after last request)
Read HW config even in model mode

View File

@@ -1,64 +0,0 @@
/*
* This file is part of the libsidservo project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdlib.h>
#include "sidservo.h"
extern conf_t Conf;
// unused arguments of functions
#define _U_ __attribute__((__unused__))
// break absent in `case`
#define FALLTHRU __attribute__ ((fallthrough))
// and synonym for FALLTHRU
#define NOBREAKHERE __attribute__ ((fallthrough))
// weak functions
#define WEAK __attribute__ ((weak))
#ifndef DBL_EPSILON
#define DBL_EPSILON (2.2204460492503131e-16)
#endif
#ifndef FALSE
#define FALSE (0)
#endif
#ifndef TRUE
#define TRUE (1)
#endif
#ifdef EBUG
#include <stdio.h>
#define COLOR_RED "\033[1;31;40m"
#define COLOR_GREEN "\033[1;32;40m"
#define COLOR_OLD "\033[0;0;0m"
#define FNAME() do{ fprintf(stderr, COLOR_GREEN "\n%s " COLOR_OLD, __func__); \
fprintf(stderr, "(%s, line %d)\n", __FILE__, __LINE__);} while(0)
#define DBG(...) do{ fprintf(stderr, COLOR_RED "\n%s " COLOR_OLD, __func__); \
fprintf(stderr, "(%s, line %d): ", __FILE__, __LINE__); \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n");} while(0)
#else // EBUG
#define FNAME()
#define DBG(...)
#endif // EBUG

View File

@@ -34,7 +34,9 @@ typedef struct{
static hardware_configuration_t HW = {0}; static hardware_configuration_t HW = {0};
static parameters G = {0}; static parameters G = {
.conffile = "servo.conf",
};
static sl_option_t cmdlnopts[] = { static sl_option_t cmdlnopts[] = {
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&G.help), "show this help"}, {"help", NO_ARGS, NULL, 'h', arg_int, APTR(&G.help), "show this help"},
@@ -53,7 +55,7 @@ static sl_option_t confopts[] = {
static void dumpaxis(char axis, axis_config_t *c){ static void dumpaxis(char axis, axis_config_t *c){
#define STRUCTPAR(p) (c)->p #define STRUCTPAR(p) (c)->p
#define DUMP(par) do{printf("%c%s=%g\n", axis, #par, STRUCTPAR(par));}while(0) #define DUMP(par) do{printf("%c%s=%.10g\n", axis, #par, STRUCTPAR(par));}while(0)
#define DUMPD(par) do{printf("%c%s=%g\n", axis, #par, RAD2DEG(STRUCTPAR(par)));}while(0) #define DUMPD(par) do{printf("%c%s=%g\n", axis, #par, RAD2DEG(STRUCTPAR(par)));}while(0)
DUMPD(accel); DUMPD(accel);
DUMPD(backlash); DUMPD(backlash);
@@ -64,6 +66,8 @@ static void dumpaxis(char axis, axis_config_t *c){
DUMP(outplimit); DUMP(outplimit);
DUMP(currlimit); DUMP(currlimit);
DUMP(intlimit); DUMP(intlimit);
DUMP(motor_stepsperrev);
DUMP(axis_stepsperrev);
#undef DUMP #undef DUMP
#undef DUMPD #undef DUMPD
} }

View File

@@ -24,25 +24,32 @@
static conf_t Config = { static conf_t Config = {
.MountDevPath = "/dev/ttyUSB0", .MountDevPath = "/dev/ttyUSB0",
.MountDevSpeed = 19200, .MountDevSpeed = 19200,
.EncoderXDevPath = "/dev/encoderX0", .EncoderXDevPath = "/dev/encoder_X0",
.EncoderYDevPath = "/dev/encoderY0", .EncoderYDevPath = "/dev/encoder_Y0",
.EncoderDevSpeed = 153000, .EncoderDevSpeed = 153000,
.MountReqInterval = 0.1, .MountReqInterval = 0.1,
.EncoderReqInterval = 0.05, .EncoderReqInterval = 0.001,
.SepEncoder = 2, .SepEncoder = 2,
.EncoderSpeedInterval = 0.1, .EncoderSpeedInterval = 0.05,
.XPIDC.P = 0.8, .EncodersDisagreement = 1e-5, // 2''
.PIDMaxDt = 1.,
.PIDRefreshDt = 0.1,
.PIDCycleDt = 5.,
.XPIDC.P = 0.5,
.XPIDC.I = 0.1, .XPIDC.I = 0.1,
.XPIDC.D = 0.3, .XPIDC.D = 0.2,
.XPIDV.P = 1., .XPIDV.P = 0.09,
.XPIDV.I = 0.01, .XPIDV.I = 0.0,
.XPIDV.D = 0.2, .XPIDV.D = 0.05,
.YPIDC.P = 0.8, .YPIDC.P = 0.5,
.YPIDC.I = 0.1, .YPIDC.I = 0.1,
.YPIDC.D = 0.3, .YPIDC.D = 0.2,
.YPIDV.P = 0.5, .YPIDV.P = 0.09,
.YPIDV.I = 0.2, .YPIDV.I = 0.0,
.YPIDV.D = 0.5, .YPIDV.D = 0.05,
.MaxPointingErr = 0.13962634,
.MaxFinePointingErr = 0.026179939,
.MaxGuidingErr = 4.8481368e-7,
}; };
static sl_option_t opts[] = { static sl_option_t opts[] = {
@@ -50,13 +57,17 @@ static sl_option_t opts[] = {
{"MountDevSpeed", NEED_ARG, NULL, 0, arg_int, APTR(&Config.MountDevSpeed), "serial speed of mount device"}, {"MountDevSpeed", NEED_ARG, NULL, 0, arg_int, APTR(&Config.MountDevSpeed), "serial speed of mount device"},
{"EncoderDevPath", NEED_ARG, NULL, 0, arg_string, APTR(&Config.EncoderDevPath), "path to encoder device"}, {"EncoderDevPath", NEED_ARG, NULL, 0, arg_string, APTR(&Config.EncoderDevPath), "path to encoder device"},
{"EncoderDevSpeed", NEED_ARG, NULL, 0, arg_int, APTR(&Config.EncoderDevSpeed), "serial speed of encoder device"}, {"EncoderDevSpeed", NEED_ARG, NULL, 0, arg_int, APTR(&Config.EncoderDevSpeed), "serial speed of encoder device"},
{"MountReqInterval",NEED_ARG, NULL, 0, arg_double, APTR(&Config.MountReqInterval), "interval of mount requests (not less than 0.05s)"},
{"EncoderReqInterval",NEED_ARG, NULL, 0, arg_double, APTR(&Config.EncoderReqInterval),"interval of encoder requests (in case of sep=2)"},
{"SepEncoder", NEED_ARG, NULL, 0, arg_int, APTR(&Config.SepEncoder), "encoder is separate device (1 - one device, 2 - two devices)"}, {"SepEncoder", NEED_ARG, NULL, 0, arg_int, APTR(&Config.SepEncoder), "encoder is separate device (1 - one device, 2 - two devices)"},
{"EncoderXDevPath", NEED_ARG, NULL, 0, arg_string, APTR(&Config.EncoderXDevPath), "path to X encoder (/dev/encoderX0)"}, {"EncoderXDevPath", NEED_ARG, NULL, 0, arg_string, APTR(&Config.EncoderXDevPath), "path to X encoder (/dev/encoderX0)"},
{"EncoderYDevPath", NEED_ARG, NULL, 0, arg_string, APTR(&Config.EncoderYDevPath), "path to Y encoder (/dev/encoderY0)"}, {"EncoderYDevPath", NEED_ARG, NULL, 0, arg_string, APTR(&Config.EncoderYDevPath), "path to Y encoder (/dev/encoderY0)"},
{"EncodersDisagreement", NEED_ARG,NULL, 0, arg_double, APTR(&Config.EncodersDisagreement),"acceptable disagreement between motor and axis encoders"},
{"MountReqInterval",NEED_ARG, NULL, 0, arg_double, APTR(&Config.MountReqInterval), "interval of mount requests (not less than 0.05s)"},
{"EncoderReqInterval",NEED_ARG, NULL, 0, arg_double, APTR(&Config.EncoderReqInterval),"interval of encoder requests (in case of sep=2)"},
{"EncoderSpeedInterval", NEED_ARG,NULL, 0, arg_double, APTR(&Config.EncoderSpeedInterval),"interval of speed calculations, s"}, {"EncoderSpeedInterval", NEED_ARG,NULL, 0, arg_double, APTR(&Config.EncoderSpeedInterval),"interval of speed calculations, s"},
{"RunModel", NEED_ARG, NULL, 0, arg_int, APTR(&Config.RunModel), "instead of real hardware run emulation"}, {"RunModel", NEED_ARG, NULL, 0, arg_int, APTR(&Config.RunModel), "instead of real hardware run emulation"},
{"PIDMaxDt", NEED_ARG, NULL, 0, arg_double, APTR(&Config.PIDMaxDt), "maximal PID refresh time interval (if larger all old data will be cleared)"},
{"PIDRefreshDt", NEED_ARG, NULL, 0, arg_double, APTR(&Config.PIDRefreshDt), "normal PID refresh interval by master process"},
{"PIDCycleDt", NEED_ARG, NULL, 0, arg_double, APTR(&Config.PIDCycleDt), "PID I cycle time (analog of \"RC\" for PID on opamps)"},
{"XPIDCP", NEED_ARG, NULL, 0, arg_double, APTR(&Config.XPIDC.P), "P of X PID (coordinate driven)"}, {"XPIDCP", NEED_ARG, NULL, 0, arg_double, APTR(&Config.XPIDC.P), "P of X PID (coordinate driven)"},
{"XPIDCI", NEED_ARG, NULL, 0, arg_double, APTR(&Config.XPIDC.I), "I of X PID (coordinate driven)"}, {"XPIDCI", NEED_ARG, NULL, 0, arg_double, APTR(&Config.XPIDC.I), "I of X PID (coordinate driven)"},
{"XPIDCD", NEED_ARG, NULL, 0, arg_double, APTR(&Config.XPIDC.D), "D of X PID (coordinate driven)"}, {"XPIDCD", NEED_ARG, NULL, 0, arg_double, APTR(&Config.XPIDC.D), "D of X PID (coordinate driven)"},
@@ -69,6 +80,12 @@ static sl_option_t opts[] = {
{"YPIDVP", NEED_ARG, NULL, 0, arg_double, APTR(&Config.YPIDV.P), "P of Y PID (velocity driven)"}, {"YPIDVP", NEED_ARG, NULL, 0, arg_double, APTR(&Config.YPIDV.P), "P of Y PID (velocity driven)"},
{"YPIDVI", NEED_ARG, NULL, 0, arg_double, APTR(&Config.YPIDV.I), "I of Y PID (velocity driven)"}, {"YPIDVI", NEED_ARG, NULL, 0, arg_double, APTR(&Config.YPIDV.I), "I of Y PID (velocity driven)"},
{"YPIDVD", NEED_ARG, NULL, 0, arg_double, APTR(&Config.YPIDV.D), "D of Y PID (velocity driven)"}, {"YPIDVD", NEED_ARG, NULL, 0, arg_double, APTR(&Config.YPIDV.D), "D of Y PID (velocity driven)"},
{"MaxPointingErr", NEED_ARG, NULL, 0, arg_double, APTR(&Config.MaxPointingErr), "if angle < this, change state from \"slewing\" to \"pointing\" (coarse pointing): 8 degrees"},
{"MaxFinePointingErr",NEED_ARG, NULL, 0, arg_double, APTR(&Config.MaxFinePointingErr), "if angle < this, chane state from \"pointing\" to \"guiding\" (fine poinging): 1.5 deg"},
{"MaxGuidingErr", NEED_ARG, NULL, 0, arg_double, APTR(&Config.MaxGuidingErr), "if error less than this value we suppose that target is captured and guiding is good (true guiding): 0.1''"},
{"XEncZero", NEED_ARG, NULL, 0, arg_int, APTR(&Config.XEncZero), "X axis encoder approximate zero position"},
{"YEncZero", NEED_ARG, NULL, 0, arg_int, APTR(&Config.YEncZero), "Y axis encoder approximate zero position"},
// {"",NEED_ARG, NULL, 0, arg_double, APTR(&Config.), ""},
end_option end_option
}; };
@@ -93,5 +110,19 @@ void dumpConf(){
} }
void confHelp(){ void confHelp(){
sl_showhelp(-1, opts); sl_conf_showhelp(-1, opts);
}
const char* errcodes[MCC_E_AMOUNT] = {
[MCC_E_OK] = "OK",
[MCC_E_FATAL] = "Fatal error",
[MCC_E_BADFORMAT] = "Wrong data format",
[MCC_E_ENCODERDEV] = "Encoder error",
[MCC_E_MOUNTDEV] = "Mount error",
[MCC_E_FAILED] = "Failed to run"
};
// return string with error code
const char *EcodeStr(mcc_errcodes_t e){
if(e >= MCC_E_AMOUNT) return "Wrong error code";
return errcodes[e];
} }

View File

@@ -25,3 +25,4 @@
void confHelp(); void confHelp();
conf_t *readServoConf(const char *filename); conf_t *readServoConf(const char *filename);
void dumpConf(); void dumpConf();
const char *EcodeStr(mcc_errcodes_t e);

View File

@@ -24,7 +24,7 @@
#include "simpleconv.h" #include "simpleconv.h"
// starting dump time (to conform different logs) // starting dump time (to conform different logs)
static double dumpT0 = -1.; static struct timespec dumpT0 = {0};
#if 0 #if 0
// amount of elements used for encoders' data filtering // amount of elements used for encoders' data filtering
@@ -63,7 +63,9 @@ static double filter(double val, int idx){
#endif #endif
// return starting time of dump // return starting time of dump
double dumpt0(){ return dumpT0; } void dumpt0(struct timespec *t){
if(t) *t = dumpT0;
}
/** /**
@@ -75,12 +77,12 @@ void logmnt(FILE *fcoords, mountdata_t *m){
if(!fcoords) return; if(!fcoords) return;
//DBG("LOG %s", m ? "data" : "header"); //DBG("LOG %s", m ? "data" : "header");
if(!m){ // write header if(!m){ // write header
fprintf(fcoords, "# time Xmot(deg) Ymot(deg) Xenc(deg) Yenc(deg) VX(d/s) VY(d/s) millis\n"); fprintf(fcoords, " time Xmot(deg) Ymot(deg) Xenc(deg) Yenc(deg) VX(d/s) VY(d/s) millis\n");
return; return;
}else if(dumpT0 < 0.) dumpT0 = m->encXposition.t; }else if(dumpT0.tv_sec == 0) dumpT0 = m->encXposition.t;
// write data // write data
fprintf(fcoords, "%12.6f %10.6f %10.6f %10.6f %10.6f %10.6f %10.6f %10u\n", fprintf(fcoords, "%12.6f %10.6f %10.6f %10.6f %10.6f %10.6f %10.6f %10u\n",
m->encXposition.t - dumpT0, RAD2DEG(m->motXposition.val), RAD2DEG(m->motYposition.val), Mount.timeDiff(&m->encXposition.t, &dumpT0), RAD2DEG(m->motXposition.val), RAD2DEG(m->motYposition.val),
RAD2DEG(m->encXposition.val), RAD2DEG(m->encYposition.val), RAD2DEG(m->encXposition.val), RAD2DEG(m->encYposition.val),
RAD2DEG(m->encXspeed.val), RAD2DEG(m->encYspeed.val), RAD2DEG(m->encXspeed.val), RAD2DEG(m->encYspeed.val),
m->millis); m->millis);
@@ -106,16 +108,17 @@ void dumpmoving(FILE *fcoords, double t, int N){
LOGWARN("Can't get mount data"); LOGWARN("Can't get mount data");
} }
uint32_t mdmillis = mdata.millis; uint32_t mdmillis = mdata.millis;
double enct = (mdata.encXposition.t + mdata.encYposition.t) / 2.; struct timespec encXt = mdata.encXposition.t;
int ctr = -1; int ctr = -1;
double xlast = mdata.motXposition.val, ylast = mdata.motYposition.val; double xlast = mdata.motXposition.val, ylast = mdata.motYposition.val;
double t0 = Mount.currentT(); double t0 = Mount.timeFromStart();
while(Mount.currentT() - t0 < t && ctr < N){ while(Mount.timeFromStart() - t0 < t && ctr < N){
usleep(1000); usleep(1000);
if(MCC_E_OK != Mount.getMountData(&mdata)){ WARNX("Can't get data"); continue;} if(MCC_E_OK != Mount.getMountData(&mdata)){ WARNX("Can't get data"); continue;}
double tmsr = (mdata.encXposition.t + mdata.encYposition.t) / 2.; //double tmsr = (mdata.encXposition.t + mdata.encYposition.t) / 2.;
if(tmsr == enct) continue; struct timespec msrt = mdata.encXposition.t;
enct = tmsr; if(msrt.tv_nsec == encXt.tv_nsec) continue;
encXt = msrt;
if(fcoords) logmnt(fcoords, &mdata); if(fcoords) logmnt(fcoords, &mdata);
if(mdata.millis == mdmillis) continue; if(mdata.millis == mdmillis) continue;
//DBG("ctr=%d, motpos=%g/%g", ctr, mdata.motXposition.val, mdata.motYposition.val); //DBG("ctr=%d, motpos=%g/%g", ctr, mdata.motXposition.val, mdata.motYposition.val);
@@ -126,7 +129,7 @@ void dumpmoving(FILE *fcoords, double t, int N){
ctr = 0; ctr = 0;
}else ++ctr; }else ++ctr;
} }
DBG("Exit dumping; tend=%g, tmon=%g", t, Mount.currentT() - t0); DBG("Exit dumping; tend=%g, tmon=%g", t, Mount.timeFromStart() - t0);
} }
/** /**
@@ -146,14 +149,6 @@ void waitmoving(int N){
millis = mdata.millis; millis = mdata.millis;
if(mdata.Xstate != AXIS_STOPPED || mdata.Ystate != AXIS_STOPPED) ctr = 0; if(mdata.Xstate != AXIS_STOPPED || mdata.Ystate != AXIS_STOPPED) ctr = 0;
else ++ctr; else ++ctr;
/*
if(mdata.motXposition.val != xlast || mdata.motYposition.val != ylast){
xlast = mdata.motXposition.val;
ylast = mdata.motYposition.val;
//DBG("x/y: %g/%g", RAD2DEG(xlast), RAD2DEG(ylast));
ctr = 0;
}else ++ctr;
*/
} }
} }

View File

@@ -27,4 +27,4 @@ void dumpmoving(FILE *fcoords, double t, int N);
void waitmoving(int N); void waitmoving(int N);
int getPos(coordval_pair_t *mot, coordval_pair_t *enc); int getPos(coordval_pair_t *mot, coordval_pair_t *enc);
void chk0(int ncycles); void chk0(int ncycles);
double dumpt0(); void dumpt0(struct timespec *t);

View File

@@ -73,6 +73,7 @@ int main(int argc, char **argv){
conf_t *Config = readServoConf(G.conffile); conf_t *Config = readServoConf(G.conffile);
if(!Config){ if(!Config){
dumpConf(); dumpConf();
confHelp();
return 1; return 1;
} }
if(G.coordsoutput){ if(G.coordsoutput){

View File

@@ -139,8 +139,10 @@ static mcc_errcodes_t return2zero(){
short_command_t cmd = {0}; short_command_t cmd = {0};
DBG("Try to move to zero"); DBG("Try to move to zero");
cmd.Xmot = 0.; cmd.Ymot = 0.; cmd.Xmot = 0.; cmd.Ymot = 0.;
cmd.Xspeed = MCC_MAX_X_SPEED; coordpair_t maxspd;
cmd.Yspeed = MCC_MAX_Y_SPEED; if(MCC_E_OK != Mount.getMaxSpeed(&maxspd)) return MCC_E_FAILED;
cmd.Xspeed = maxspd.X;
cmd.Yspeed = maxspd.Y;
/*cmd.xychange = 1; /*cmd.xychange = 1;
cmd.XBits = 100; cmd.XBits = 100;
cmd.YBits = 20;*/ cmd.YBits = 20;*/

View File

@@ -83,7 +83,7 @@ void waithalf(double t){
uint32_t millis = 0; uint32_t millis = 0;
double xlast = 0., ylast = 0.; double xlast = 0., ylast = 0.;
while(ctr < 5){ while(ctr < 5){
if(Mount.currentT() >= t) return; if(Mount.timeFromStart() >= t) return;
usleep(1000); usleep(1000);
if(MCC_E_OK != Mount.getMountData(&mdata)){ WARNX("Can't get data"); continue;} if(MCC_E_OK != Mount.getMountData(&mdata)){ WARNX("Can't get data"); continue;}
if(mdata.millis == millis) continue; if(mdata.millis == millis) continue;
@@ -158,24 +158,24 @@ int main(int argc, char **argv){
}else{ }else{
tagX = 0.; tagY = DEG2RAD(G.amplitude); tagX = 0.; tagY = DEG2RAD(G.amplitude);
} }
double t = Mount.currentT(), t0 = t; double t = Mount.timeFromStart(), t0 = t;
coordpair_t tag = {.X = tagX, .Y = tagY}, rtag = {.X = -tagX, .Y = -tagY}; coordpair_t tag = {.X = tagX, .Y = tagY}, rtag = {.X = -tagX, .Y = -tagY};
double divide = 2.; double divide = 2.;
for(int i = 0; i < G.Nswings; ++i){ for(int i = 0; i < G.Nswings; ++i){
Mount.moveTo(&tag); Mount.moveTo(&tag);
DBG("CMD: %g", Mount.currentT()-t0); DBG("CMD: %g", Mount.timeFromStart()-t0);
t += G.period / divide; t += G.period / divide;
divide = 1.; divide = 1.;
waithalf(t); waithalf(t);
DBG("Moved to +, t=%g", t-t0); DBG("Moved to +, t=%g", t-t0);
DBG("CMD: %g", Mount.currentT()-t0); DBG("CMD: %g", Mount.timeFromStart()-t0);
Mount.moveTo(&rtag); Mount.moveTo(&rtag);
t += G.period; t += G.period;
waithalf(t); waithalf(t);
DBG("Moved to -, t=%g", t-t0); DBG("Moved to -, t=%g", t-t0);
DBG("CMD: %g", Mount.currentT()-t0); DBG("CMD: %g", Mount.timeFromStart()-t0);
} }
green("Move to zero @ %g\n", Mount.currentT()); green("Move to zero @ %g\n", Mount.timeFromStart());
tag = (coordpair_t){0}; tag = (coordpair_t){0};
// be sure to move @ 0,0 // be sure to move @ 0,0
if(MCC_E_OK != Mount.moveTo(&tag)){ if(MCC_E_OK != Mount.moveTo(&tag)){

View File

@@ -91,8 +91,7 @@ int main(int _U_ argc, char _U_ **argv){
if(MCC_E_OK != Mount.init(Config)) ERRX("Can't init mount"); if(MCC_E_OK != Mount.init(Config)) ERRX("Can't init mount");
coordval_pair_t M, E; coordval_pair_t M, E;
if(!getPos(&M, &E)) ERRX("Can't get current position"); if(!getPos(&M, &E)) ERRX("Can't get current position");
printf("Current time: %.10f\n", Mount.currentT()); printf("Current time: %.10f\n", Mount.timeFromStart());
DBG("xt: %g, x: %g", M.X.t, M.X.val);
if(G.coordsoutput){ if(G.coordsoutput){
if(!G.wait) green("When logging I should wait until moving ends; added '-w'\n"); if(!G.wait) green("When logging I should wait until moving ends; added '-w'\n");
G.wait = 1; G.wait = 1;
@@ -121,7 +120,11 @@ int main(int _U_ argc, char _U_ **argv){
} }
printf("Moving to X=%gdeg, Y=%gdeg\n", G.X, G.Y); printf("Moving to X=%gdeg, Y=%gdeg\n", G.X, G.Y);
tag.X = DEG2RAD(G.X); tag.Y = DEG2RAD(G.Y); tag.X = DEG2RAD(G.X); tag.Y = DEG2RAD(G.Y);
Mount.moveTo(&tag); mcc_errcodes_t e = Mount.moveTo(&tag);
if(MCC_E_OK != e){
WARNX("Cant go to given coordinates: %s\n", EcodeStr(e));
goto out;
}
if(G.wait){ if(G.wait){
sleep(1); sleep(1);
waitmoving(G.Ncycles); waitmoving(G.Ncycles);
@@ -133,7 +136,9 @@ out:
if(G.coordsoutput) pthread_join(dthr, NULL); if(G.coordsoutput) pthread_join(dthr, NULL);
DBG("QUIT"); DBG("QUIT");
if(G.wait){ if(G.wait){
if(getPos(&M, NULL)) printf("Mount position: X=%g, Y=%g\n", RAD2DEG(M.X.val), RAD2DEG(M.Y.val)); usleep(250000); // pause to refresh coordinates
if(getPos(&M, &E)) printf("Mount position: X=%g, Y=%g; encoders: X=%g, Y=%g\n", RAD2DEG(M.X.val), RAD2DEG(M.Y.val),
RAD2DEG(E.X.val), RAD2DEG(E.Y.val));
Mount.quit(); Mount.quit();
} }
return 0; return 0;

View File

@@ -44,6 +44,7 @@ typedef struct{
char *conffile; char *conffile;
} parameters; } parameters;
static conf_t *Config = NULL;
static FILE *fcoords = NULL, *errlog = NULL; static FILE *fcoords = NULL, *errlog = NULL;
static pthread_t dthr; static pthread_t dthr;
static parameters G = { static parameters G = {
@@ -96,38 +97,35 @@ static void runtraectory(traectory_fn tfn){
if(!tfn) return; if(!tfn) return;
coordval_pair_t telXY; coordval_pair_t telXY;
coordval_pair_t target; coordval_pair_t target;
coordpair_t traectXY, endpoint; coordpair_t traectXY;
endpoint.X = G.Xmax, endpoint.Y = G.Ymax; double tlast = 0., tstart = Mount.timeFromStart();
double t0 = dumpt0(), tlast = 0., tstart = Mount.currentT(); long tlastXnsec = 0, tlastYnsec = 0;
double tlastX = 0., tlastY = 0.; struct timespec tcur, t0 = {0};
dumpt0(&t0);
while(1){ while(1){
if(!telpos(&telXY)){ if(!telpos(&telXY)){
WARNX("No next telescope position"); WARNX("No next telescope position");
return; return;
} }
if(telXY.X.t == tlastX && telXY.Y.t == tlastY) continue; // last measure - don't mind if(!Mount.currentT(&tcur)) continue;
DBG("\n\nTELPOS: %g'/%g' (%.6f/%.6f) measured @ %.6f/%.6f", RAD2AMIN(telXY.X.val), RAD2AMIN(telXY.Y.val), RAD2DEG(telXY.X.val), RAD2DEG(telXY.Y.val), telXY.X.t, telXY.Y.t); if(telXY.X.t.tv_nsec == tlastXnsec && telXY.Y.t.tv_nsec == tlastYnsec) continue; // last measure - don't mind
tlastX = telXY.X.t; tlastY = telXY.Y.t; DBG("\n\nTELPOS: %g'/%g' (%.6f/%.6f)", RAD2AMIN(telXY.X.val), RAD2AMIN(telXY.Y.val), RAD2DEG(telXY.X.val), RAD2DEG(telXY.Y.val));
double t = Mount.currentT(); tlastXnsec = telXY.X.t.tv_nsec; tlastYnsec = telXY.Y.t.tv_nsec;
double t = Mount.timeFromStart();
if(fabs(telXY.X.val) > G.Xmax || fabs(telXY.Y.val) > G.Ymax || t - tstart > G.tmax) break; if(fabs(telXY.X.val) > G.Xmax || fabs(telXY.Y.val) > G.Ymax || t - tstart > G.tmax) break;
if(!traectory_point(&traectXY, t)) break; if(!traectory_point(&traectXY, t)) break;
target.X.val = traectXY.X; target.Y.val = traectXY.Y; target.X.val = traectXY.X; target.Y.val = traectXY.Y;
target.X.t = target.Y.t = t; target.X.t = target.Y.t = tcur;
// check whether we should change direction if(t0.tv_nsec == 0 && t0.tv_sec == 0) dumpt0(&t0);
if(telXY.X.val > traectXY.X) endpoint.X = -G.Xmax;
else if(telXY.X.val < traectXY.X) endpoint.X = G.Xmax;
if(telXY.Y.val > traectXY.Y) endpoint.Y = -G.Ymax;
else if(telXY.Y.val < traectXY.Y) endpoint.Y = G.Ymax;
if(t0 < 0.) t0 = dumpt0();
else{ else{
//DBG("target: %g'/%g'", RAD2AMIN(traectXY.X), RAD2AMIN(traectXY.Y)); //DBG("target: %g'/%g'", RAD2AMIN(traectXY.X), RAD2AMIN(traectXY.Y));
DBG("%g: dX=%.4f'', dY=%.4f''", t-t0, RAD2ASEC(traectXY.X-telXY.X.val), RAD2ASEC(traectXY.Y-telXY.Y.val)); DBG("%g: dX=%.4f'', dY=%.4f''", t-tstart, RAD2ASEC(traectXY.X-telXY.X.val), RAD2ASEC(traectXY.Y-telXY.Y.val));
//DBG("Correct to: %g/%g with EP %g/%g", RAD2DEG(target.X.val), RAD2DEG(target.Y.val), RAD2DEG(endpoint.X), RAD2DEG(endpoint.Y)); //DBG("Correct to: %g/%g with EP %g/%g", RAD2DEG(target.X.val), RAD2DEG(target.Y.val), RAD2DEG(endpoint.X), RAD2DEG(endpoint.Y));
if(errlog) if(errlog)
fprintf(errlog, "%10.4f %10.4f %10.4f\n", telXY.X.t-t0, RAD2ASEC(traectXY.X-telXY.X.val), RAD2ASEC(traectXY.Y-telXY.Y.val)); fprintf(errlog, "%10.4f %10.4f %10.4f\n", Mount.timeDiff(&telXY.X.t, &t0), RAD2ASEC(traectXY.X-telXY.X.val), RAD2ASEC(traectXY.Y-telXY.Y.val));
} }
if(MCC_E_OK != Mount.correctTo(&target, &endpoint)) WARNX("Error of correction!"); if(MCC_E_OK != Mount.correctTo(&target)) WARNX("Error of correction!");
while((t = Mount.currentT()) - tlast < MCC_PID_REFRESH_DT) usleep(50); while((t = Mount.timeFromStart()) - tlast < Config->PIDRefreshDt) usleep(500);
tlast = t; tlast = t;
} }
WARNX("No next traectory point or emulation ends"); WARNX("No next traectory point or emulation ends");
@@ -153,7 +151,7 @@ int main(int argc, char **argv){
if(!(fcoords = fopen(G.coordsoutput, "w"))) if(!(fcoords = fopen(G.coordsoutput, "w")))
ERRX("Can't open %s", G.coordsoutput); ERRX("Can't open %s", G.coordsoutput);
}else fcoords = stdout; }else fcoords = stdout;
conf_t *Config = readServoConf(G.conffile); Config = readServoConf(G.conffile);
if(!Config || G.dumpconf){ if(!Config || G.dumpconf){
dumpConf(); dumpConf();
return 1; return 1;

View File

@@ -41,7 +41,7 @@ int init_traectory(traectory_fn f, coordpair_t *XY0){
if(!f || !XY0) return FALSE; if(!f || !XY0) return FALSE;
cur_traectory = f; cur_traectory = f;
XYstart = *XY0; XYstart = *XY0;
tstart = Mount.currentT(); tstart = Mount.timeFromStart();
mountdata_t mdata; mountdata_t mdata;
int ntries = 0; int ntries = 0;
for(; ntries < 10; ++ntries){ for(; ntries < 10; ++ntries){
@@ -98,7 +98,7 @@ int Linear(coordpair_t *nextpt, double t){
int SinCos(coordpair_t *nextpt, double t){ int SinCos(coordpair_t *nextpt, double t){
coordpair_t pt; coordpair_t pt;
pt.X = XYstart.X + ASEC2RAD(5.) * sin((t-tstart)/30.*2*M_PI); pt.X = XYstart.X + ASEC2RAD(5.) * sin((t-tstart)/30.*2*M_PI);
pt.Y = XYstart.Y + AMIN2RAD(10.)* cos((t-tstart)/200.*2*M_PI); pt.Y = XYstart.Y + AMIN2RAD(1.)* cos((t-tstart)/200.*2*M_PI);
if(nextpt) *nextpt = pt; if(nextpt) *nextpt = pt;
return TRUE; return TRUE;
} }

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject> <!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 17.0.2, 2025-11-01T14:58:43. --> <!-- Written by QtCreator 18.0.0, 2026-03-11T12:36:26. -->
<qtcreator> <qtcreator>
<data> <data>
<variable>EnvironmentId</variable> <variable>EnvironmentId</variable>
@@ -86,6 +86,7 @@
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/> <valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
<value type="bool" key="ClangTools.UseGlobalSettings">true</value> <value type="bool" key="ClangTools.UseGlobalSettings">true</value>
</valuemap> </valuemap>
<value type="int" key="RcSync">0</value>
</valuemap> </valuemap>
</data> </data>
<data> <data>
@@ -164,6 +165,7 @@
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value> <value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value> <value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value> <value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value> <value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap> </valuemap>
@@ -198,6 +200,7 @@
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value> <value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value> <value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value> <value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value> <value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap> </valuemap>
@@ -208,10 +211,6 @@
<variable>ProjectExplorer.Project.TargetCount</variable> <variable>ProjectExplorer.Project.TargetCount</variable>
<value type="qlonglong">1</value> <value type="qlonglong">1</value>
</data> </data>
<data>
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
<value type="int">22</value>
</data>
<data> <data>
<variable>Version</variable> <variable>Version</variable>
<value type="int">22</value> <value type="int">22</value>

View File

@@ -22,6 +22,7 @@ examples/traectories.h
main.h main.h
movingmodel.c movingmodel.c
movingmodel.h movingmodel.h
polltest/main.c
ramp.c ramp.c
ramp.h ramp.h
serial.h serial.h

View File

@@ -25,6 +25,7 @@
#include <time.h> #include <time.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h>
#include "main.h" #include "main.h"
#include "movingmodel.h" #include "movingmodel.h"
@@ -32,39 +33,82 @@
#include "ssii.h" #include "ssii.h"
#include "PID.h" #include "PID.h"
// adder for monotonic time by realtime: inited any call of init()
static struct timespec timeadder = {0}, // adder of CLOCK_REALTIME to CLOCK_MONOTONIC
t0 = {0}, // curtime() for initstarttime() call
starttime = {0}; // starting time by monotonic (for timefromstart())
conf_t Conf = {0}; conf_t Conf = {0};
// parameters for model // parameters for model
static movemodel_t *Xmodel, *Ymodel; static movemodel_t *Xmodel, *Ymodel;
// limits for model and/or real mount (in latter case data should be read from mount on init)
// radians, rad/sec, rad/sec^2 // radians, rad/sec, rad/sec^2
static limits_t // max speeds (rad/s): xs=10 deg/s, ys=8 deg/s
// accelerations: xa=12.6 deg/s^2, ya= 9.5 deg/s^2
limits_t
Xlimits = { Xlimits = {
.min = {.coord = -3.1241, .speed = 1e-10, .accel = 1e-6}, .min = {.coord = -3.1241, .speed = 1e-10, .accel = 1e-6},
.max = {.coord = 3.1241, .speed = MCC_MAX_X_SPEED, .accel = MCC_X_ACCELERATION}}, .max = {.coord = 3.1241, .speed = 0.174533, .accel = 0.219911}},
Ylimits = { Ylimits = {
.min = {.coord = -3.1241, .speed = 1e-10, .accel = 1e-6}, .min = {.coord = -3.1241, .speed = 1e-10, .accel = 1e-6},
.max = {.coord = 3.1241, .speed = MCC_MAX_Y_SPEED, .accel = MCC_Y_ACCELERATION}} .max = {.coord = 3.1241, .speed = 0.139626, .accel = 0.165806}}
; ;
static mcc_errcodes_t shortcmd(short_command_t *cmd); static mcc_errcodes_t shortcmd(short_command_t *cmd);
static mcc_errcodes_t get_hwconf(hardware_configuration_t *hwConfig);
/** /**
* @brief nanotime - monotonic time from first run * @brief curtime - monotonic time from first run
* @return time in seconds * @param t - struct timespec by CLOCK_MONOTONIC but with setpoint by CLOCK_REALTIME on observations start
* @return TRUE if all OK
* FIXME: double -> struct timespec; on init: init t0 by CLOCK_REALTIME
*/ */
double nanotime(){ int curtime(struct timespec *t){
static double t0 = -1.; struct timespec now;
if(clock_gettime(CLOCK_MONOTONIC, &now)) return FALSE;
now.tv_sec += timeadder.tv_sec;
now.tv_nsec += timeadder.tv_nsec;
if(now.tv_nsec > 999999999L){
++now.tv_sec;
now.tv_nsec -= 1000000000L;
}
if(t) *t = now;
return TRUE;
}
// init starttime; @return TRUE if all OK
static int initstarttime(){
struct timespec start;
if(clock_gettime(CLOCK_MONOTONIC, &starttime)) return FALSE;
if(clock_gettime(CLOCK_REALTIME, &start)) return FALSE;
timeadder.tv_sec = start.tv_sec - starttime.tv_sec;
timeadder.tv_nsec = start.tv_nsec - starttime.tv_nsec;
if(timeadder.tv_nsec < 0){
--timeadder.tv_sec;
timeadder.tv_nsec += 1000000000L;
}
curtime(&t0);
return TRUE;
}
// return difference (in seconds) between time1 and time0
double timediff(const struct timespec *time1, const struct timespec *time0){
if(!time1 || !time0) return -1.;
return (time1->tv_sec - time0->tv_sec) + (time1->tv_nsec - time0->tv_nsec) / 1e9;
}
// difference between given time and last initstarttime() call
double timediff0(const struct timespec *time1){
return timediff(time1, &t0);
}
// time from last initstarttime() call
double timefromstart(){
struct timespec now; struct timespec now;
if(clock_gettime(CLOCK_MONOTONIC, &now)) return -1.; if(clock_gettime(CLOCK_MONOTONIC, &now)) return -1.;
if(t0 < 0.){ return (now.tv_sec - starttime.tv_sec) + (now.tv_nsec - starttime.tv_nsec) / 1e9;
struct timespec start;
if(clock_gettime(CLOCK_REALTIME, &start)) return -1.;
t0 = (double)start.tv_sec + (double)start.tv_nsec * 1e-9
- (double)now.tv_sec + (double)now.tv_nsec * 1e-9;
}
return (double)now.tv_sec + (double)now.tv_nsec * 1e-9 + t0;
} }
/** /**
* @brief quit - close all opened and return to default state * @brief quit - close all opened and return to default state
* TODO: close serial devices even in "model" mode
*/ */
static void quit(){ static void quit(){
if(Conf.RunModel) return; if(Conf.RunModel) return;
@@ -74,18 +118,17 @@ static void quit(){
DBG("Exit"); DBG("Exit");
} }
void getModData(coordval_pair_t *c, movestate_t *xst, movestate_t *yst){ void getModData(coordpair_t *c, movestate_t *xst, movestate_t *yst){
if(!c || !Xmodel || !Ymodel) return; if(!c || !Xmodel || !Ymodel) return;
double tnow = nanotime(); double tnow = timefromstart();
moveparam_t Xp, Yp; moveparam_t Xp, Yp;
movestate_t Xst = Xmodel->get_state(Xmodel, &Xp); movestate_t Xst = Xmodel->get_state(Xmodel, &Xp);
//DBG("Xstate = %d", Xst); //DBG("Xstate = %d", Xst);
if(Xst == ST_MOVE) Xst = Xmodel->proc_move(Xmodel, &Xp, tnow); if(Xst == ST_MOVE) Xst = Xmodel->proc_move(Xmodel, &Xp, tnow);
movestate_t Yst = Ymodel->get_state(Ymodel, &Yp); movestate_t Yst = Ymodel->get_state(Ymodel, &Yp);
if(Yst == ST_MOVE) Yst = Ymodel->proc_move(Ymodel, &Yp, tnow); if(Yst == ST_MOVE) Yst = Ymodel->proc_move(Ymodel, &Yp, tnow);
c->X.t = c->Y.t = tnow; c->X = Xp.coord;
c->X.val = Xp.coord; c->Y = Yp.coord;
c->Y.val = Yp.coord;
if(xst) *xst = Xst; if(xst) *xst = Xst;
if(yst) *yst = Yst; if(yst) *yst = Yst;
} }
@@ -118,6 +161,8 @@ double LS_calc_slope(less_square_t *l, double x, double t){
if(!l) return 0.; if(!l) return 0.;
size_t idx = l->idx; size_t idx = l->idx;
double oldx = l->x[idx], oldt = l->t[idx], oldt2 = l->t2[idx], oldxt = l->xt[idx]; double oldx = l->x[idx], oldt = l->t[idx], oldt2 = l->t2[idx], oldxt = l->xt[idx];
/*DBG("old: x=%g, t=%g, t2=%g, xt=%g; sum: %g, t=%g, t2=%g, xt=%g", oldx, oldt, oldt2, oldxt,
l->xsum, l->tsum, l->t2sum, l->xtsum);*/
double t2 = t * t, xt = x * t; double t2 = t * t, xt = x * t;
l->x[idx] = x; l->t2[idx] = t2; l->x[idx] = x; l->t2[idx] = t2;
l->t[idx] = t; l->xt[idx] = xt; l->t[idx] = t; l->xt[idx] = xt;
@@ -129,14 +174,13 @@ double LS_calc_slope(less_square_t *l, double x, double t){
l->xtsum += xt - oldxt; l->xtsum += xt - oldxt;
double n = (double)l->arraysz; double n = (double)l->arraysz;
double denominator = n * l->t2sum - l->tsum * l->tsum; double denominator = n * l->t2sum - l->tsum * l->tsum;
//DBG("idx=%zd, arrsz=%zd, den=%g", l->idx, l->arraysz, denominator);
if(fabs(denominator) < 1e-7) return 0.; if(fabs(denominator) < 1e-7) return 0.;
double numerator = n * l->xtsum - l->xsum * l->tsum; double numerator = n * l->xtsum - l->xsum * l->tsum;
//DBG("x=%g, t=%g; idx=%zd, arrsz=%zd, den=%g; xsum=%g, num=%g", x, t, l->idx, l->arraysz, denominator, l->xsum, numerator);
// point: (sum_x - slope * sum_t) / n; // point: (sum_x - slope * sum_t) / n;
return (numerator / denominator); return (numerator / denominator);
} }
/** /**
* @brief init - open serial devices and do other job * @brief init - open serial devices and do other job
* @param c - initial configuration * @param c - initial configuration
@@ -145,15 +189,20 @@ double LS_calc_slope(less_square_t *l, double x, double t){
static mcc_errcodes_t init(conf_t *c){ static mcc_errcodes_t init(conf_t *c){
FNAME(); FNAME();
if(!c) return MCC_E_BADFORMAT; if(!c) return MCC_E_BADFORMAT;
if(!initstarttime()) return MCC_E_FAILED;
Conf = *c; Conf = *c;
mcc_errcodes_t ret = MCC_E_OK; mcc_errcodes_t ret = MCC_E_OK;
Xmodel = model_init(&Xlimits); Xmodel = model_init(&Xlimits);
Ymodel = model_init(&Ylimits); Ymodel = model_init(&Ylimits);
if(Conf.MountReqInterval > 1. || Conf.MountReqInterval < 0.05){
DBG("Bad value of MountReqInterval");
ret = MCC_E_BADFORMAT;
}
if(Conf.RunModel){ if(Conf.RunModel){
if(!Xmodel || !Ymodel || !openMount()) return MCC_E_FAILED; if(!Xmodel || !Ymodel || !openMount()) return MCC_E_FAILED;
return MCC_E_OK; return MCC_E_OK;
} }
if(!Conf.MountDevPath || Conf.MountDevSpeed < 1200){ if(!Conf.MountDevPath || Conf.MountDevSpeed < MOUNT_BAUDRATE_MIN){
DBG("Define mount device path and speed"); DBG("Define mount device path and speed");
ret = MCC_E_BADFORMAT; ret = MCC_E_BADFORMAT;
}else if(!openMount()){ }else if(!openMount()){
@@ -169,41 +218,47 @@ static mcc_errcodes_t init(conf_t *c){
ret = MCC_E_ENCODERDEV; ret = MCC_E_ENCODERDEV;
} }
} }
if(Conf.MountReqInterval > 1. || Conf.MountReqInterval < 0.05){ // TODO: read hardware configuration on init
DBG("Bad value of MountReqInterval");
ret = MCC_E_BADFORMAT;
}
if(Conf.EncoderSpeedInterval < Conf.EncoderReqInterval * MCC_CONF_MIN_SPEEDC || Conf.EncoderSpeedInterval > MCC_CONF_MAX_SPEEDINT){ if(Conf.EncoderSpeedInterval < Conf.EncoderReqInterval * MCC_CONF_MIN_SPEEDC || Conf.EncoderSpeedInterval > MCC_CONF_MAX_SPEEDINT){
DBG("Wrong speed interval"); DBG("Wrong speed interval");
ret = MCC_E_BADFORMAT; ret = MCC_E_BADFORMAT;
} }
//uint8_t buf[1024];
//data_t d = {.buf = buf, .len = 0, .maxlen = 1024};
if(!SSrawcmd(CMD_EXITACM, NULL)) ret = MCC_E_FAILED; if(!SSrawcmd(CMD_EXITACM, NULL)) ret = MCC_E_FAILED;
if(ret != MCC_E_OK) return ret; if(ret != MCC_E_OK) return ret;
return updateMotorPos(); // read HW config to update constants
hardware_configuration_t HW;
if(MCC_E_OK != get_hwconf(&HW)) return MCC_E_FAILED;
// make a pause for actual encoder's values
double t0 = timefromstart();
while(timefromstart() - t0 < Conf.EncoderReqInterval) usleep(1000);
mcc_errcodes_t e = updateMotorPos();
// and refresh data after updating
DBG("Wait for next mount reading");
t0 = timefromstart();
while(timefromstart() - t0 < Conf.MountReqInterval * 3.) usleep(1000);
return e;
} }
// check coordinates (rad) and speeds (rad/s); return FALSE if failed // check coordinates (rad) and speeds (rad/s); return FALSE if failed
// TODO fix to real limits!!! // TODO fix to real limits!!!
static int chkX(double X){ static int chkX(double X){
if(X > 2.*M_PI || X < -2.*M_PI) return FALSE; if(X > Xlimits.max.coord || X < Xlimits.min.coord) return FALSE;
return TRUE; return TRUE;
} }
static int chkY(double Y){ static int chkY(double Y){
if(Y > 2.*M_PI || Y < -2.*M_PI) return FALSE; if(Y > Ylimits.max.coord || Y < Ylimits.min.coord) return FALSE;
return TRUE; return TRUE;
} }
static int chkXs(double s){ static int chkXs(double s){
if(s < 0. || s > MCC_MAX_X_SPEED) return FALSE; if(s < Xlimits.min.speed || s > Xlimits.max.speed) return FALSE;
return TRUE; return TRUE;
} }
static int chkYs(double s){ static int chkYs(double s){
if(s < 0. || s > MCC_MAX_Y_SPEED) return FALSE; if(s < Ylimits.min.speed || s > Ylimits.max.speed) return FALSE;
return TRUE; return TRUE;
} }
// set SLEWING state if axis was stopped later // set SLEWING state if axis was stopped
static void setslewingstate(){ static void setslewingstate(){
//FNAME(); //FNAME();
mountdata_t d; mountdata_t d;
@@ -219,19 +274,6 @@ static void setslewingstate(){
}else DBG("CAN't GET MOUNT DATA!"); }else DBG("CAN't GET MOUNT DATA!");
} }
/*
static mcc_errcodes_t slew2(const coordpair_t *target, slewflags_t flags){
(void)target;
(void)flags;
//if(Conf.RunModel) return ... ;
if(MCC_E_OK != updateMotorPos()) return MCC_E_FAILED;
//...
setStat(AXIS_SLEWING, AXIS_SLEWING);
//...
return MCC_E_FAILED;
}
*/
/** /**
* @brief move2 - simple move to given point and stop * @brief move2 - simple move to given point and stop
* @param X - new X coordinate (radians: -pi..pi) or NULL * @param X - new X coordinate (radians: -pi..pi) or NULL
@@ -246,12 +288,13 @@ static mcc_errcodes_t move2(const coordpair_t *target){
DBG("x,y: %g, %g", target->X, target->Y); DBG("x,y: %g, %g", target->X, target->Y);
cmd.Xmot = target->X; cmd.Xmot = target->X;
cmd.Ymot = target->Y; cmd.Ymot = target->Y;
cmd.Xspeed = MCC_MAX_X_SPEED; cmd.Xspeed = Xlimits.max.speed;
cmd.Yspeed = MCC_MAX_Y_SPEED; cmd.Yspeed = Ylimits.max.speed;
mcc_errcodes_t r = shortcmd(&cmd); /*mcc_errcodes_t r = shortcmd(&cmd);
if(r != MCC_E_OK) return r; if(r != MCC_E_OK) return r;
setslewingstate(); setslewingstate();
return MCC_E_OK; return MCC_E_OK;*/
return shortcmd(&cmd);
} }
/** /**
@@ -280,6 +323,7 @@ static mcc_errcodes_t move2s(const coordpair_t *target, const coordpair_t *speed
if(!target || !speed) return MCC_E_BADFORMAT; if(!target || !speed) return MCC_E_BADFORMAT;
if(!chkX(target->X) || !chkY(target->Y)) return MCC_E_BADFORMAT; if(!chkX(target->X) || !chkY(target->Y)) return MCC_E_BADFORMAT;
if(!chkXs(speed->X) || !chkYs(speed->Y)) return MCC_E_BADFORMAT; if(!chkXs(speed->X) || !chkYs(speed->Y)) return MCC_E_BADFORMAT;
// updateMotorPos() here can make a problem; TODO: remove?
if(MCC_E_OK != updateMotorPos()) return MCC_E_FAILED; if(MCC_E_OK != updateMotorPos()) return MCC_E_FAILED;
short_command_t cmd = {0}; short_command_t cmd = {0};
cmd.Xmot = target->X; cmd.Xmot = target->X;
@@ -299,7 +343,7 @@ static mcc_errcodes_t move2s(const coordpair_t *target, const coordpair_t *speed
static mcc_errcodes_t emstop(){ static mcc_errcodes_t emstop(){
FNAME(); FNAME();
if(Conf.RunModel){ if(Conf.RunModel){
double curt = nanotime(); double curt = timefromstart();
Xmodel->emergency_stop(Xmodel, curt); Xmodel->emergency_stop(Xmodel, curt);
Ymodel->emergency_stop(Ymodel, curt); Ymodel->emergency_stop(Ymodel, curt);
return MCC_E_OK; return MCC_E_OK;
@@ -311,7 +355,7 @@ static mcc_errcodes_t emstop(){
static mcc_errcodes_t stop(){ static mcc_errcodes_t stop(){
FNAME(); FNAME();
if(Conf.RunModel){ if(Conf.RunModel){
double curt = nanotime(); double curt = timefromstart();
Xmodel->stop(Xmodel, curt); Xmodel->stop(Xmodel, curt);
Ymodel->stop(Ymodel,curt); Ymodel->stop(Ymodel,curt);
return MCC_E_OK; return MCC_E_OK;
@@ -328,7 +372,7 @@ static mcc_errcodes_t stop(){
static mcc_errcodes_t shortcmd(short_command_t *cmd){ static mcc_errcodes_t shortcmd(short_command_t *cmd){
if(!cmd) return MCC_E_BADFORMAT; if(!cmd) return MCC_E_BADFORMAT;
if(Conf.RunModel){ if(Conf.RunModel){
double curt = nanotime(); double curt = timefromstart();
moveparam_t param = {0}; moveparam_t param = {0};
param.coord = cmd->Xmot; param.speed = cmd->Xspeed; param.coord = cmd->Xmot; param.speed = cmd->Xspeed;
if(!model_move2(Xmodel, &param, curt)) return MCC_E_FAILED; if(!model_move2(Xmodel, &param, curt)) return MCC_E_FAILED;
@@ -360,7 +404,7 @@ static mcc_errcodes_t shortcmd(short_command_t *cmd){
static mcc_errcodes_t longcmd(long_command_t *cmd){ static mcc_errcodes_t longcmd(long_command_t *cmd){
if(!cmd) return MCC_E_BADFORMAT; if(!cmd) return MCC_E_BADFORMAT;
if(Conf.RunModel){ if(Conf.RunModel){
double curt = nanotime(); double curt = timefromstart();
moveparam_t param = {0}; moveparam_t param = {0};
param.coord = cmd->Xmot; param.speed = cmd->Xspeed; param.coord = cmd->Xmot; param.speed = cmd->Xspeed;
if(!model_move2(Xmodel, &param, curt)) return MCC_E_FAILED; if(!model_move2(Xmodel, &param, curt)) return MCC_E_FAILED;
@@ -387,6 +431,7 @@ static mcc_errcodes_t get_hwconf(hardware_configuration_t *hwConfig){
if(!hwConfig) return MCC_E_BADFORMAT; if(!hwConfig) return MCC_E_BADFORMAT;
if(Conf.RunModel) return MCC_E_FAILED; if(Conf.RunModel) return MCC_E_FAILED;
SSconfig config; SSconfig config;
DBG("Read HW configuration");
if(!cmdC(&config, FALSE)) return MCC_E_FAILED; if(!cmdC(&config, FALSE)) return MCC_E_FAILED;
// Convert acceleration (ticks per loop^2 to rad/s^2) // Convert acceleration (ticks per loop^2 to rad/s^2)
hwConfig->Xconf.accel = X_MOTACC2RS(config.Xconf.accel); hwConfig->Xconf.accel = X_MOTACC2RS(config.Xconf.accel);
@@ -426,8 +471,8 @@ static mcc_errcodes_t get_hwconf(hardware_configuration_t *hwConfig){
// Copy ticks per revolution // Copy ticks per revolution
hwConfig->Xsetpr = __bswap_32(config.Xsetpr); hwConfig->Xsetpr = __bswap_32(config.Xsetpr);
hwConfig->Ysetpr = __bswap_32(config.Ysetpr); hwConfig->Ysetpr = __bswap_32(config.Ysetpr);
hwConfig->Xmetpr = __bswap_32(config.Xmetpr) / 4; // as documentation said, real ticks are 4 times less hwConfig->Xmetpr = __bswap_32(config.Xmetpr); // as documentation said, real ticks are 4 times less
hwConfig->Ymetpr = __bswap_32(config.Ymetpr) / 4; hwConfig->Ymetpr = __bswap_32(config.Ymetpr);
// Convert slew rates (ticks per loop to rad/s) // Convert slew rates (ticks per loop to rad/s)
hwConfig->Xslewrate = X_MOTSPD2RS(config.Xslewrate); hwConfig->Xslewrate = X_MOTSPD2RS(config.Xslewrate);
hwConfig->Yslewrate = Y_MOTSPD2RS(config.Yslewrate); hwConfig->Yslewrate = Y_MOTSPD2RS(config.Yslewrate);
@@ -445,6 +490,30 @@ static mcc_errcodes_t get_hwconf(hardware_configuration_t *hwConfig){
hwConfig->locsspeed = (double)config.locsspeed * M_PI / (180.0 * 3600.0); hwConfig->locsspeed = (double)config.locsspeed * M_PI / (180.0 * 3600.0);
// Convert backlash speed (ticks per loop to rad/s) // Convert backlash speed (ticks per loop to rad/s)
hwConfig->backlspd = X_MOTSPD2RS(config.backlspd); hwConfig->backlspd = X_MOTSPD2RS(config.backlspd);
// now read text commands
int64_t i64;
double Xticks, Yticks;
DBG("SERIAL");
// motor's encoder ticks per rev
if(!SSgetint(CMD_MEPRX, &i64)) return MCC_E_FAILED;
Xticks = ((double) i64); // divide by 4 as these values stored ???
if(!SSgetint(CMD_MEPRY, &i64)) return MCC_E_FAILED;
Yticks = ((double) i64);
X_ENC_ZERO = Conf.XEncZero;
Y_ENC_ZERO = Conf.YEncZero;
DBG("xyrev: %d/%d", config.xbits.motrev, config.ybits.motrev);
X_MOT_STEPSPERREV = hwConfig->Xconf.motor_stepsperrev = Xticks; // (config.xbits.motrev) ? -Xticks : Xticks;
Y_MOT_STEPSPERREV = hwConfig->Yconf.motor_stepsperrev = Yticks; //(config.ybits.motrev) ? -Yticks : Yticks;
DBG("zero: %d/%d; motsteps: %.10g/%.10g", X_ENC_ZERO, Y_ENC_ZERO, X_MOT_STEPSPERREV, Y_MOT_STEPSPERREV);
// axis encoder ticks per rev
if(!SSgetint(CMD_AEPRX, &i64)) return MCC_E_FAILED;
Xticks = (double) i64;
if(!SSgetint(CMD_AEPRY, &i64)) return MCC_E_FAILED;
Yticks = (double) i64;
DBG("xyencrev: %d/%d", config.xbits.encrev, config.ybits.encrev);
X_ENC_STEPSPERREV = hwConfig->Xconf.axis_stepsperrev = (config.xbits.encrev) ? -Xticks : Xticks;
Y_ENC_STEPSPERREV = hwConfig->Yconf.axis_stepsperrev = (config.ybits.encrev) ? -Yticks : Yticks;
DBG("encsteps: %.10g/%.10g", X_ENC_STEPSPERREV, Y_ENC_STEPSPERREV);
return MCC_E_OK; return MCC_E_OK;
} }
@@ -500,17 +569,37 @@ static mcc_errcodes_t write_hwconf(hardware_configuration_t *hwConfig){
config.Ysetpr = __bswap_32(hwConfig->Ysetpr); config.Ysetpr = __bswap_32(hwConfig->Ysetpr);
config.Xmetpr = __bswap_32(hwConfig->Xmetpr); config.Xmetpr = __bswap_32(hwConfig->Xmetpr);
config.Ymetpr = __bswap_32(hwConfig->Ymetpr); config.Ymetpr = __bswap_32(hwConfig->Ymetpr);
// todo - also write text params
// TODO - next // TODO - next
(void) config; (void) config;
return MCC_E_OK; return MCC_E_OK;
} }
// getters of max/min speed and acceleration
mcc_errcodes_t maxspeed(coordpair_t *v){
if(!v) return MCC_E_BADFORMAT;
v->X = Xlimits.max.speed;
v->Y = Ylimits.max.speed;
return MCC_E_OK;
}
mcc_errcodes_t minspeed(coordpair_t *v){
if(!v) return MCC_E_BADFORMAT;
v->X = Xlimits.min.speed;
v->Y = Ylimits.min.speed;
return MCC_E_OK;
}
mcc_errcodes_t acceleration(coordpair_t *a){
if(!a) return MCC_E_BADFORMAT;
a->X = Xlimits.max.accel;
a->Y = Ylimits.max.accel;
return MCC_E_OK;
}
// init mount class // init mount class
mount_t Mount = { mount_t Mount = {
.init = init, .init = init,
.quit = quit, .quit = quit,
.getMountData = getMD, .getMountData = getMD,
// .slewTo = slew2,
.moveTo = move2, .moveTo = move2,
.moveWspeed = move2s, .moveWspeed = move2s,
.setSpeed = setspeed, .setSpeed = setspeed,
@@ -520,7 +609,13 @@ mount_t Mount = {
.longCmd = longcmd, .longCmd = longcmd,
.getHWconfig = get_hwconf, .getHWconfig = get_hwconf,
.saveHWconfig = write_hwconf, .saveHWconfig = write_hwconf,
.currentT = nanotime, .currentT = curtime,
.timeFromStart = timefromstart,
.timeDiff = timediff,
.timeDiff0 = timediff0,
.correctTo = correct2, .correctTo = correct2,
.getMaxSpeed = maxspeed,
.getMinSpeed = minspeed,
.getAcceleration = acceleration,
}; };

View File

@@ -28,8 +28,12 @@
#include "sidservo.h" #include "sidservo.h"
extern conf_t Conf; extern conf_t Conf;
double nanotime(); extern limits_t Xlimits, Ylimits;
void getModData(coordval_pair_t *c, movestate_t *xst, movestate_t *yst); int curtime(struct timespec *t);
double timediff(const struct timespec *time1, const struct timespec *time0);
double timediff0(const struct timespec *time1);
double timefromstart();
void getModData(coordpair_t *c, movestate_t *xst, movestate_t *yst);
typedef struct{ typedef struct{
double *x, *t, *t2, *xt; // arrays of coord/time and multiply double *x, *t, *t2, *xt; // arrays of coord/time and multiply
double xsum, tsum, t2sum, xtsum; // sums of coord/time and their multiply double xsum, tsum, t2sum, xtsum; // sums of coord/time and their multiply
@@ -43,10 +47,6 @@ double LS_calc_slope(less_square_t *l, double x, double t);
// unused arguments of functions // unused arguments of functions
#define _U_ __attribute__((__unused__)) #define _U_ __attribute__((__unused__))
// break absent in `case`
#define FALLTHRU __attribute__ ((fallthrough))
// and synonym for FALLTHRU
#define NOBREAKHERE __attribute__ ((fallthrough))
// weak functions // weak functions
#define WEAK __attribute__ ((weak)) #define WEAK __attribute__ ((weak))

View File

@@ -60,7 +60,7 @@ movemodel_t *model_init(limits_t *l){
int model_move2(movemodel_t *model, moveparam_t *target, double t){ int model_move2(movemodel_t *model, moveparam_t *target, double t){
if(!target || !model) return FALSE; if(!target || !model) return FALSE;
DBG("MOVE to %g at speed %g", target->coord, target->speed); DBG("MOVE to %g (deg) at speed %g (deg/s)", target->coord/M_PI*180., target->speed/M_PI*180.);
// only positive velocity // only positive velocity
if(target->speed < 0.) target->speed = -target->speed; if(target->speed < 0.) target->speed = -target->speed;
if(fabs(target->speed) < model->Min.speed){ if(fabs(target->speed) < model->Min.speed){

View File

@@ -44,7 +44,7 @@ typedef struct{
typedef struct{ typedef struct{
moveparam_t min; moveparam_t min;
moveparam_t max; moveparam_t max;
double acceleration; //double acceleration;
} limits_t; } limits_t;
typedef enum{ typedef enum{

197
LibSidServo/polltest/main.c Normal file
View File

@@ -0,0 +1,197 @@
/*
* This file is part of the libsidservo project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <usefull_macros.h>
// suppose that we ONLY poll data
#define XYBUFSZ (128)
struct{
int help;
char *Xpath;
char *Ypath;
double dt;
} G = {
.Xpath = "/dev/encoder_X0",
.Ypath = "/dev/encoder_Y0",
.dt = 0.001,
};
sl_option_t options[] = {
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&G.help), "show this help"},
{"Xpath", NEED_ARG, NULL, 'X', arg_string, APTR(&G.Xpath), "path to X encoder"},
{"Ypath", NEED_ARG, NULL, 'Y', arg_string, APTR(&G.Ypath), "path to Y encoder"},
{"dt", NEED_ARG, NULL, 'd', arg_double, APTR(&G.dt), "request interval (1e-4..10s)"},
};
typedef struct{
char buf[XYBUFSZ+1];
int len;
} buf_t;
static int Xfd = -1, Yfd = -1;
void signals(int sig){
if(sig){
signal(sig, SIG_IGN);
DBG("Get signal %d, quit.\n", sig);
}
DBG("close");
if(Xfd > 0){ close(Xfd); Xfd = -1; }
if(Yfd > 0){ close(Yfd); Yfd = -1; }
exit(sig);
}
static int op(const char *nm){
int fd = open(nm, O_RDWR|O_NOCTTY|O_NONBLOCK);
if(fd < 0) ERR("Can't open %s", nm);
struct termios2 tty;
if(ioctl(fd, TCGETS2, &tty)) ERR("Can't read TTY settings");
tty.c_lflag = 0; // ~(ICANON | ECHO | ECHOE | ISIG)
tty.c_iflag = 0; // don't do any changes in input stream
tty.c_oflag = 0; // don't do any changes in output stream
tty.c_cflag = BOTHER | CS8 | CREAD | CLOCAL; // other speed, 8bit, RW, ignore line ctrl
tty.c_ispeed = 1000000;
tty.c_ospeed = 1000000;
if(ioctl(fd, TCSETS2, &tty)) ERR("Can't set TTY settings");
// try to set exclusive
if(ioctl(fd, TIOCEXCL)){DBG("Can't make exclusive");}
return fd;
}
// write to buffer next data portion; return FALSE in case of error
static int readstrings(buf_t *buf, int fd){
FNAME();
if(!buf){WARNX("Empty buffer"); return FALSE;}
int L = XYBUFSZ - buf->len;
if(L == 0){
DBG("buffer overfull!", buf->len);
char *lastn = strrchr(buf->buf, '\n');
if(lastn){
fprintf(stderr, "BUFOVR: _%s_", buf->buf);
++lastn;
buf->len = XYBUFSZ - (lastn - buf->buf);
DBG("Memmove %d", buf->len);
memmove(lastn, buf->buf, buf->len);
buf->buf[buf->len] = 0;
}else buf->len = 0;
L = XYBUFSZ - buf->len;
}
int got = read(fd, &buf->buf[buf->len], L);
if(got < 0){
WARN("read()");
return FALSE;
}else if(got == 0){ DBG("NO data"); return TRUE; }
buf->len += got;
buf->buf[buf->len] = 0;
DBG("buf[%d]: %s", buf->len, buf->buf);
return TRUE;
}
// return TRUE if got, FALSE if no data found
static int getdata(buf_t *buf, long *out){
if(!buf || buf->len < 1) return FALSE;
// read record between last '\n' and previous (or start of string)
char *last = &buf->buf[buf->len - 1];
//DBG("buf: _%s_", buf->buf);
if(*last != '\n') return FALSE;
*last = 0;
//DBG("buf: _%s_", buf->buf);
char *prev = strrchr(buf->buf, '\n');
if(!prev) prev = buf->buf;
else{
fprintf(stderr, "MORETHANONE: _%s_", buf->buf);
++prev; // after last '\n'
}
if(out) *out = atol(prev);
// clear buffer
buf->len = 0;
return TRUE;
}
// try to write '\n' asking new data portion; return FALSE if failed
static int asknext(int fd){
FNAME();
if(fd < 0) return FALSE;
int i = 0;
for(; i < 5; ++i){
int l = write(fd, "\n", 1);
//DBG("l=%d", l);
if(1 == l) return TRUE;
usleep(100);
}
DBG("5 tries... failed!");
return FALSE;
}
int main(int argc, char **argv){
buf_t xbuf, ybuf;
long xlast, ylast;
double xtlast, ytlast;
sl_init();
sl_parseargs(&argc, &argv, options);
if(G.help) sl_showhelp(-1, options);
if(G.dt < 1e-4) ERRX("dx too small");
if(G.dt > 10.) ERRX("dx too big");
Xfd = op(G.Xpath);
Yfd = op(G.Ypath);
struct pollfd pfds[2];
pfds[0].fd = Xfd; pfds[0].events = POLLIN;
pfds[1].fd = Yfd; pfds[1].events = POLLIN;
double t0x, t0y, tstart;
asknext(Xfd); asknext(Yfd);
t0x = t0y = tstart = sl_dtime();
DBG("Start");
do{ // main cycle
if(poll(pfds, 2, 0) < 0){
WARN("poll()");
break;
}
if(pfds[0].revents && POLLIN){
DBG("got X");
if(!readstrings(&xbuf, Xfd)) break;
}
if(pfds[1].revents && POLLIN){
DBG("got Y");
if(!readstrings(&ybuf, Yfd)) break;
}
double curt = sl_dtime();
if(getdata(&xbuf, &xlast)) xtlast = curt;
if(curt - t0x >= G.dt){ // get last records
if(curt - xtlast < 1.5*G.dt)
printf("%-14.4fX=%ld\n", xtlast-tstart, xlast);
if(!asknext(Xfd)) break;
t0x = (curt - t0x < 2.*G.dt) ? t0x + G.dt : curt;
}
curt = sl_dtime();
if(getdata(&ybuf, &ylast)) ytlast = curt;
if(curt - t0y >= G.dt){ // get last records
if(curt - ytlast < 1.5*G.dt)
printf("%-14.4fY=%ld\n", ytlast-tstart, ylast);
if(!asknext(Yfd)) break;
t0y = (curt - t0y < 2.*G.dt) ? t0y + G.dt : curt;
}
}while(Xfd > 0 && Yfd > 0);
DBG("OOps: disconnected");
signals(0);
return 0;
}

View File

@@ -20,6 +20,7 @@
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <math.h> #include <math.h>
#include <poll.h>
#include <pthread.h> #include <pthread.h>
#include <signal.h> #include <signal.h>
#include <stdint.h> #include <stdint.h>
@@ -48,7 +49,7 @@ static pthread_mutex_t mntmutex = PTHREAD_MUTEX_INITIALIZER,
// encoders thread and mount thread // encoders thread and mount thread
static pthread_t encthread, mntthread; static pthread_t encthread, mntthread;
// max timeout for 1.5 bytes of encoder and 2 bytes of mount - for `select` // max timeout for 1.5 bytes of encoder and 2 bytes of mount - for `select`
static struct timeval encRtmout = {.tv_sec = 0, .tv_usec = 50000}, mntRtmout = {.tv_sec = 0, .tv_usec = 50000}; static struct timeval encRtmout = {.tv_sec = 0, .tv_usec = 100}, mntRtmout = {.tv_sec = 0, .tv_usec = 50000};
// encoders raw data // encoders raw data
typedef struct __attribute__((packed)){ typedef struct __attribute__((packed)){
uint8_t magick; uint8_t magick;
@@ -59,49 +60,30 @@ typedef struct __attribute__((packed)){
// calculate current X/Y speeds // calculate current X/Y speeds
void getXspeed(){ void getXspeed(){
static double t0 = -1.; // time of start - eliminate problem of very large times in squares
if(t0 < 0.) t0 = mountdata.encXposition.t;
static less_square_t *ls = NULL; static less_square_t *ls = NULL;
if(!ls){ if(!ls){
ls = LS_init(Conf.EncoderSpeedInterval / Conf.EncoderReqInterval); ls = LS_init(Conf.EncoderSpeedInterval / Conf.EncoderReqInterval);
if(!ls) return; if(!ls) return;
} }
pthread_mutex_lock(&datamutex); double dt = timediff0(&mountdata.encXposition.t);
double speed = LS_calc_slope(ls, mountdata.encXposition.val, mountdata.encXposition.t - t0); double speed = LS_calc_slope(ls, mountdata.encXposition.val, dt);
if(fabs(speed) < 1.5 * MCC_MAX_X_SPEED){ if(fabs(speed) < 1.5 * Xlimits.max.speed){
mountdata.encXspeed.val = speed; mountdata.encXspeed.val = speed;
mountdata.encXspeed.t = mountdata.encXposition.t; mountdata.encXspeed.t = mountdata.encXposition.t;
} }
pthread_mutex_unlock(&datamutex);
//DBG("Xspeed=%g", mountdata.encXspeed.val);
#if 0
mountdata.encXspeed.val = (mountdata.encXposition.val - lastXenc.val) / (t - lastXenc.t);
mountdata.encXspeed.t = (lastXenc.t + mountdata.encXposition.t) / 2.;
lastXenc.val = mountdata.encXposition.val;
lastXenc.t = t;
#endif
} }
void getYspeed(){ void getYspeed(){
static double t0 = -1.; // time of start - eliminate problem of very large times in squares
if(t0 < 0.) t0 = mountdata.encXposition.t;
static less_square_t *ls = NULL; static less_square_t *ls = NULL;
if(!ls){ if(!ls){
ls = LS_init(Conf.EncoderSpeedInterval / Conf.EncoderReqInterval); ls = LS_init(Conf.EncoderSpeedInterval / Conf.EncoderReqInterval);
if(!ls) return; if(!ls) return;
} }
pthread_mutex_lock(&datamutex); double dt = timediff0(&mountdata.encYposition.t);
double speed = LS_calc_slope(ls, mountdata.encYposition.val, mountdata.encYposition.t - t0); double speed = LS_calc_slope(ls, mountdata.encYposition.val, dt);
if(fabs(speed) < 1.5 * MCC_MAX_Y_SPEED){ if(fabs(speed) < 1.5 * Ylimits.max.speed){
mountdata.encYspeed.val = speed; mountdata.encYspeed.val = speed;
mountdata.encYspeed.t = mountdata.encYposition.t; mountdata.encYspeed.t = mountdata.encYposition.t;
} }
pthread_mutex_unlock(&datamutex);
#if 0
mountdata.encYspeed.val = (mountdata.encYposition.val - lastYenc.val) / (t - lastYenc.t);
mountdata.encYspeed.t = (lastYenc.t + mountdata.encYposition.t) / 2.;
lastYenc.val = mountdata.encYposition.val;
lastYenc.t = t;
#endif
} }
/** /**
@@ -109,7 +91,8 @@ void getYspeed(){
* @param databuf - input buffer with 13 bytes of data * @param databuf - input buffer with 13 bytes of data
* @param t - time when databuf[0] got * @param t - time when databuf[0] got
*/ */
static void parse_encbuf(uint8_t databuf[ENC_DATALEN], double t){ static void parse_encbuf(uint8_t databuf[ENC_DATALEN], struct timespec *t){
if(!t) return;
enc_t *edata = (enc_t*) databuf; enc_t *edata = (enc_t*) databuf;
/* /*
#ifdef EBUG #ifdef EBUG
@@ -144,18 +127,17 @@ static void parse_encbuf(uint8_t databuf[ENC_DATALEN], double t){
return; return;
} }
pthread_mutex_lock(&datamutex); pthread_mutex_lock(&datamutex);
mountdata.encXposition.val = X_ENC2RAD(edata->encX); mountdata.encXposition.val = Xenc2rad(edata->encX);
mountdata.encYposition.val = Y_ENC2RAD(edata->encY); mountdata.encYposition.val = Yenc2rad(edata->encY);
DBG("Got positions X/Y= %.6g / %.6g", mountdata.encXposition.val, mountdata.encYposition.val); DBG("Got positions X/Y= %.6g / %.6g", mountdata.encXposition.val, mountdata.encYposition.val);
mountdata.encXposition.t = t; mountdata.encXposition.t = *t;
mountdata.encYposition.t = t; mountdata.encYposition.t = *t;
//if(t - lastXenc.t > Conf.EncoderSpeedInterval) getXspeed();
//if(t - lastYenc.t > Conf.EncoderSpeedInterval) getYspeed();
getXspeed(); getYspeed(); getXspeed(); getYspeed();
pthread_mutex_unlock(&datamutex); pthread_mutex_unlock(&datamutex);
//DBG("time = %zd+%zd/1e6, X=%g deg, Y=%g deg", tv->tv_sec, tv->tv_usec, mountdata.encposition.X*180./M_PI, mountdata.encposition.Y*180./M_PI); //DBG("time = %zd+%zd/1e6, X=%g deg, Y=%g deg", tv->tv_sec, tv->tv_usec, mountdata.encposition.X*180./M_PI, mountdata.encposition.Y*180./M_PI);
} }
#if 0
/** /**
* @brief getencval - get uint64_t data from encoder * @brief getencval - get uint64_t data from encoder
* @param fd - encoder fd * @param fd - encoder fd
@@ -163,33 +145,53 @@ static void parse_encbuf(uint8_t databuf[ENC_DATALEN], double t){
* @param t - measurement time * @param t - measurement time
* @return amount of data read or 0 if problem * @return amount of data read or 0 if problem
*/ */
static int getencval(int fd, double *val, double *t){ static int getencval(int fd, double *val, struct timespec *t){
if(fd < 0) return FALSE; if(fd < 0){
DBG("Encoder fd < 0!");
return FALSE;
}
char buf[128]; char buf[128];
int got = 0, Lmax = 127; int got = 0, Lmax = 127;
double t0 = nanotime(); double t0 = timefromstart();
//DBG("start: %.6g", t0);
do{ do{
fd_set rfds; fd_set rfds;
FD_ZERO(&rfds); FD_ZERO(&rfds);
FD_SET(fd, &rfds); FD_SET(fd, &rfds);
struct timeval tv = encRtmout; struct timeval tv = encRtmout;
int retval = select(fd + 1, &rfds, NULL, NULL, &tv); int retval = select(fd + 1, &rfds, NULL, NULL, &tv);
if(!retval) continue; if(!retval){
//DBG("select()==0 - timeout, %.6g", timefromstart());
break;
}
if(retval < 0){ if(retval < 0){
if(errno == EINTR) continue; if(errno == EINTR){
DBG("EINTR");
continue;
}
DBG("select() < 0");
return 0; return 0;
} }
if(FD_ISSET(fd, &rfds)){ if(FD_ISSET(fd, &rfds)){
ssize_t l = read(fd, &buf[got], Lmax); ssize_t l = read(fd, &buf[got], Lmax);
if(l < 1) return 0; // disconnected ?? if(l < 1){
DBG("read() < 0");
return 0; // disconnected ??
}
got += l; Lmax -= l; got += l; Lmax -= l;
buf[got] = 0; buf[got] = 0;
} else continue; } else continue;
if(strchr(buf, '\n')) break; if(buf[got-1] == '\n') break; // got EOL as last symbol
}while(Lmax && nanotime() - t0 < Conf.EncoderReqInterval); }while(Lmax && timefromstart() - t0 < Conf.EncoderReqInterval / 5.);
if(got == 0) return 0; // WTF? if(got == 0){
//DBG("No data from encoder, tfs=%.6g", timefromstart());
return 0;
}
char *estr = strrchr(buf, '\n'); char *estr = strrchr(buf, '\n');
if(!estr) return 0; if(!estr){
DBG("No EOL");
return 0;
}
*estr = 0; *estr = 0;
char *bgn = strrchr(buf, '\n'); char *bgn = strrchr(buf, '\n');
if(bgn) ++bgn; if(bgn) ++bgn;
@@ -201,9 +203,11 @@ static int getencval(int fd, double *val, double *t){
return 0; // wrong number return 0; // wrong number
} }
if(val) *val = (double) data; if(val) *val = (double) data;
if(t) *t = t0; if(t){ if(!curtime(t)){ DBG("Can't get time"); return 0; }}
return got; return got;
} }
#endif
// try to read 1 byte from encoder; return -1 if nothing to read or -2 if device seems to be disconnected // try to read 1 byte from encoder; return -1 if nothing to read or -2 if device seems to be disconnected
static int getencbyte(){ static int getencbyte(){
if(encfd[0] < 0) return -1; if(encfd[0] < 0) return -1;
@@ -265,8 +269,6 @@ static void clrmntbuf(){
if(mntfd < 0) return; if(mntfd < 0) return;
uint8_t byte; uint8_t byte;
fd_set rfds; fd_set rfds;
//double t0 = nanotime();
//int n = 0;
do{ do{
FD_ZERO(&rfds); FD_ZERO(&rfds);
FD_SET(mntfd, &rfds); FD_SET(mntfd, &rfds);
@@ -280,10 +282,8 @@ static void clrmntbuf(){
if(FD_ISSET(mntfd, &rfds)){ if(FD_ISSET(mntfd, &rfds)){
ssize_t l = read(mntfd, &byte, 1); ssize_t l = read(mntfd, &byte, 1);
if(l != 1) break; if(l != 1) break;
//++n;
} else break; } else break;
}while(1); }while(1);
//DBG("Cleared by %g (got %d bytes)", nanotime() - t0, n);
} }
// main encoder thread (for separate encoder): read next data and make parsing // main encoder thread (for separate encoder): read next data and make parsing
@@ -291,7 +291,7 @@ static void *encoderthread1(void _U_ *u){
if(Conf.SepEncoder != 1) return NULL; if(Conf.SepEncoder != 1) return NULL;
uint8_t databuf[ENC_DATALEN]; uint8_t databuf[ENC_DATALEN];
int wridx = 0, errctr = 0; int wridx = 0, errctr = 0;
double t = 0.; struct timespec tcur;
while(encfd[0] > -1 && errctr < MAX_ERR_CTR){ while(encfd[0] > -1 && errctr < MAX_ERR_CTR){
int b = getencbyte(); int b = getencbyte();
if(b == -2) ++errctr; if(b == -2) ++errctr;
@@ -302,13 +302,14 @@ static void *encoderthread1(void _U_ *u){
if((uint8_t)b == ENC_MAGICK){ if((uint8_t)b == ENC_MAGICK){
// DBG("Got magic -> start filling packet"); // DBG("Got magic -> start filling packet");
databuf[wridx++] = (uint8_t) b; databuf[wridx++] = (uint8_t) b;
t = nanotime();
} }
continue; continue;
}else databuf[wridx++] = (uint8_t) b; }else databuf[wridx++] = (uint8_t) b;
if(wridx == ENC_DATALEN){ if(wridx == ENC_DATALEN){
parse_encbuf(databuf, t); if(curtime(&tcur)){
wridx = 0; parse_encbuf(databuf, &tcur);
wridx = 0;
}
} }
} }
if(encfd[0] > -1){ if(encfd[0] > -1){
@@ -318,53 +319,138 @@ static void *encoderthread1(void _U_ *u){
return NULL; return NULL;
} }
#define XYBUFSZ (128)
typedef struct{
char buf[XYBUFSZ+1];
int len;
} buf_t;
// write to buffer next data portion; return FALSE in case of error
static int readstrings(buf_t *buf, int fd){
if(!buf){DBG("Empty buffer"); return FALSE;}
int L = XYBUFSZ - buf->len;
if(L < 0){
DBG("buf not initialized!");
buf->len = 0;
}
if(L == 0){
DBG("buffer overfull: %d!", buf->len);
char *lastn = strrchr(buf->buf, '\n');
if(lastn){
fprintf(stderr, "BUFOVR: _%s_", buf->buf);
++lastn;
buf->len = XYBUFSZ - (lastn - buf->buf);
DBG("Memmove %d", buf->len);
memmove(lastn, buf->buf, buf->len);
buf->buf[buf->len] = 0;
}else buf->len = 0;
L = XYBUFSZ - buf->len;
}
//DBG("read %d bytes from %d", L, fd);
int got = read(fd, &buf->buf[buf->len], L);
if(got < 0){
DBG("read()");
return FALSE;
}else if(got == 0){ DBG("NO data"); return TRUE; }
buf->len += got;
buf->buf[buf->len] = 0;
//DBG("buf[%d]: %s", buf->len, buf->buf);
return TRUE;
}
// return TRUE if got, FALSE if no data found
static int getdata(buf_t *buf, long *out){
if(!buf || buf->len < 1 || buf->len > (XYBUFSZ+1)) return FALSE;
// read record between last '\n' and previous (or start of string)
char *last = &buf->buf[buf->len - 1];
//DBG("buf: _%s_", buf->buf);
if(*last != '\n') return FALSE;
*last = 0;
//DBG("buf: _%s_", buf->buf);
char *prev = strrchr(buf->buf, '\n');
if(!prev) prev = buf->buf;
else{
fprintf(stderr, "MORETHANONE: _%s_", buf->buf);
++prev; // after last '\n'
}
if(out) *out = atol(prev);
// clear buffer
buf->len = 0;
return TRUE;
}
// try to write '\n' asking new data portion; return FALSE if failed
static int asknext(int fd){
//FNAME();
if(fd < 0) return FALSE;
int i = 0;
for(; i < 5; ++i){
int l = write(fd, "\n", 1);
//DBG("l=%d", l);
if(1 == l) return TRUE;
usleep(100);
}
DBG("5 tries... failed!");
return FALSE;
}
// main encoder thread for separate encoders as USB devices /dev/encoder_X0 and /dev/encoder_Y0 // main encoder thread for separate encoders as USB devices /dev/encoder_X0 and /dev/encoder_Y0
static void *encoderthread2(void _U_ *u){ static void *encoderthread2(void _U_ *u){
if(Conf.SepEncoder != 2) return NULL; if(Conf.SepEncoder != 2) return NULL;
DBG("Thread started"); DBG("Thread started");
struct pollfd pfds[2];
pfds[0].fd = encfd[0]; pfds[0].events = POLLIN;
pfds[1].fd = encfd[1]; pfds[1].events = POLLIN;
double t0[2], tstart;
buf_t strbuf[2] = {0};
long msrlast[2]; // last encoder data
double mtlast[2]; // last measurement time
asknext(encfd[0]); asknext(encfd[1]);
t0[0] = t0[1] = tstart = timefromstart();
int errctr = 0; int errctr = 0;
double t0 = nanotime(); do{ // main cycle
const char *req = "\n"; if(poll(pfds, 2, 0) < 0){
int need2ask = 0; // need or not to ask encoder for new data DBG("poll()");
while(encfd[0] > -1 && encfd[1] > -1 && errctr < MAX_ERR_CTR){ break;
if(need2ask){
if(1 != write(encfd[0], req, 1)) { ++errctr; continue; }
else if(1 != write(encfd[1], req, 1)) { ++errctr; continue; }
} }
double v, t; int got = 0;
if(getencval(encfd[0], &v, &t)){ for(int i = 0; i < 2; ++i){
pthread_mutex_lock(&datamutex); if(pfds[i].revents && POLLIN){
mountdata.encXposition.val = X_ENC2RAD(v); if(!readstrings(&strbuf[i], encfd[i])){
//DBG("encX(%g) = %g", t, mountdata.encXposition.val); ++errctr;
mountdata.encXposition.t = t; break;
pthread_mutex_unlock(&datamutex); }
//if(t - lastXenc.t > Conf.EncoderSpeedInterval) getXspeed(); }
getXspeed(); double curt = timefromstart();
if(getencval(encfd[1], &v, &t)){ if(getdata(&strbuf[i], &msrlast[i])) mtlast[i] = curt;
pthread_mutex_lock(&datamutex); if(curt - t0[i] >= Conf.EncoderReqInterval){ // get last records
mountdata.encYposition.val = Y_ENC2RAD(v); if(curt - mtlast[i] < 1.5*Conf.EncoderReqInterval){
//DBG("encY(%g) = %g", t, mountdata.encYposition.val); pthread_mutex_lock(&datamutex);
mountdata.encYposition.t = t; if(i == 0){
pthread_mutex_unlock(&datamutex); mountdata.encXposition.val = Xenc2rad((double)msrlast[i]);
//if(t - lastYenc.t > Conf.EncoderSpeedInterval) getYspeed(); curtime(&mountdata.encXposition.t);
getYspeed(); /*DBG("msrlast=%ld, Xpos.val=%g, t=%zd; XEzero=%d, SPR=%g",
errctr = 0; msrlast[i], mountdata.encXposition.val, mountdata.encXposition.t.tv_sec,
need2ask = 0; X_ENC_ZERO, X_ENC_STEPSPERREV);*/
} else { getXspeed();
if(need2ask) ++errctr; }else{
else need2ask = 1; mountdata.encYposition.val = Yenc2rad((double)msrlast[i]);
continue; curtime(&mountdata.encYposition.t);
getYspeed();
}
pthread_mutex_unlock(&datamutex);
}
if(!asknext(encfd[i])){
++errctr;
break;
}
t0[i] = (curt - t0[i] < 2.*Conf.EncoderReqInterval) ? t0[i] + Conf.EncoderReqInterval : curt;
++got;
} }
} else {
if(need2ask) ++errctr;
else need2ask = 1;
continue;
} }
while(nanotime() - t0 < Conf.EncoderReqInterval){ usleep(50); } if(got == 2) errctr = 0;
//DBG("DT=%g (RI=%g)", nanotime()-t0, Conf.EncoderReqInterval); }while(encfd[0] > -1 && encfd[1] > -1 && errctr < MAX_ERR_CTR);
t0 = nanotime(); DBG("\n\nEXIT: ERRCTR=%d", errctr);
}
DBG("ERRCTR=%d", errctr);
for(int i = 0; i < 2; ++i){ for(int i = 0; i < 2; ++i){
if(encfd[i] > -1){ if(encfd[i] > -1){
close(encfd[i]); close(encfd[i]);
@@ -391,13 +477,14 @@ void data_free(data_t **x){
} }
static void chkModStopped(double *prev, double cur, int *nstopped, axis_status_t *stat){ static void chkModStopped(double *prev, double cur, int *nstopped, axis_status_t *stat){
if(!prev || !nstopped || !stat) return;
if(isnan(*prev)){ if(isnan(*prev)){
*stat = AXIS_STOPPED; *stat = AXIS_STOPPED;
DBG("START"); DBG("START");
}else if(*stat != AXIS_STOPPED){ }else if(*stat != AXIS_STOPPED){
if(fabs(*prev - cur) < DBL_EPSILON && ++(*nstopped) > MOTOR_STOPPED_CNT){ if(fabs(*prev - cur) < DBL_EPSILON && ++(*nstopped) > MOTOR_STOPPED_CNT){
*stat = AXIS_STOPPED; *stat = AXIS_STOPPED;
DBG("AXIS stopped"); DBG("AXIS stopped; prev=%g, cur=%g; nstopped=%d", *prev/M_PI*180., cur/M_PI*180., *nstopped);
} }
}else if(*prev != cur){ }else if(*prev != cur){
DBG("AXIS moving"); DBG("AXIS moving");
@@ -412,42 +499,43 @@ static void *mountthread(void _U_ *u){
uint8_t buf[2*sizeof(SSstat)]; uint8_t buf[2*sizeof(SSstat)];
SSstat *status = (SSstat*) buf; SSstat *status = (SSstat*) buf;
bzero(&mountdata, sizeof(mountdata)); bzero(&mountdata, sizeof(mountdata));
double t0 = nanotime(), tstart = t0; double t0 = timefromstart(), tstart = t0, tcur = t0;
static double oldmt = -100.; // old `millis measurement` time double oldmt = -100.; // old `millis measurement` time
static uint32_t oldmillis = 0; static uint32_t oldmillis = 0;
if(Conf.RunModel){ if(Conf.RunModel){
double Xprev = NAN, Yprev = NAN; // previous coordinates double Xprev = NAN, Yprev = NAN; // previous coordinates
int xcnt = 0, ycnt = 0; int xcnt = 0, ycnt = 0;
while(1){ while(1){
coordval_pair_t c; coordpair_t c;
movestate_t xst, yst; movestate_t xst, yst;
// now change data // now change data
getModData(&c, &xst, &yst); getModData(&c, &xst, &yst);
struct timespec tnow;
if(!curtime(&tnow) || (tcur = timefromstart()) < 0.) continue;
pthread_mutex_lock(&datamutex); pthread_mutex_lock(&datamutex);
double tnow = c.X.t;
mountdata.encXposition.t = mountdata.encYposition.t = tnow; mountdata.encXposition.t = mountdata.encYposition.t = tnow;
mountdata.encXposition.val = c.X.val; mountdata.encXposition.val = c.X + (drand48() - 0.5)*1e-6; // .2arcsec error
mountdata.encYposition.val = c.Y.val; mountdata.encYposition.val = c.Y + (drand48() - 0.5)*1e-6;
//DBG("t=%g, X=%g, Y=%g", tnow, c.X.val, c.Y.val); //DBG("t=%g, X=%g, Y=%g", tnow, c.X.val, c.Y.val);
if(tnow - oldmt > Conf.MountReqInterval){ if(tcur - oldmt > Conf.MountReqInterval){
oldmillis = mountdata.millis = (uint32_t)((tnow - tstart) * 1e3); oldmillis = mountdata.millis = (uint32_t)((tcur - tstart) * 1e3);
mountdata.motYposition.t = mountdata.motXposition.t = tnow; mountdata.motYposition.t = mountdata.motXposition.t = tnow;
if(xst == ST_MOVE) if(xst == ST_MOVE)
mountdata.motXposition.val = c.X.val + (c.X.val - mountdata.motXposition.val)*(drand48() - 0.5)/100.; mountdata.motXposition.val = c.X + (c.X - mountdata.motXposition.val)*(drand48() - 0.5)/100.;
else //else
mountdata.motXposition.val = c.X.val; // mountdata.motXposition.val = c.X;
if(yst == ST_MOVE) if(yst == ST_MOVE)
mountdata.motYposition.val = c.Y.val + (c.Y.val - mountdata.motYposition.val)*(drand48() - 0.5)/100.; mountdata.motYposition.val = c.Y + (c.Y - mountdata.motYposition.val)*(drand48() - 0.5)/100.;
else //else
mountdata.motYposition.val = c.Y.val; // mountdata.motYposition.val = c.Y;
oldmt = tnow; oldmt = tcur;
}else mountdata.millis = oldmillis; }else mountdata.millis = oldmillis;
chkModStopped(&Xprev, c.X.val, &xcnt, &mountdata.Xstate); chkModStopped(&Xprev, c.X, &xcnt, &mountdata.Xstate);
chkModStopped(&Yprev, c.Y.val, &ycnt, &mountdata.Ystate); chkModStopped(&Yprev, c.Y, &ycnt, &mountdata.Ystate);
pthread_mutex_unlock(&datamutex);
getXspeed(); getYspeed(); getXspeed(); getYspeed();
while(nanotime() - t0 < Conf.EncoderReqInterval) usleep(50); pthread_mutex_unlock(&datamutex);
t0 = nanotime(); while(timefromstart() - t0 < Conf.EncoderReqInterval) usleep(50);
t0 = timefromstart();
} }
} }
// data to get // data to get
@@ -457,31 +545,8 @@ static void *mountthread(void _U_ *u){
if(!cmd_getstat) goto failed; if(!cmd_getstat) goto failed;
while(mntfd > -1 && errctr < MAX_ERR_CTR){ while(mntfd > -1 && errctr < MAX_ERR_CTR){
// read data to status // read data to status
double t0 = nanotime(); struct timespec tcur;
#if 0 if(!curtime(&tcur)) continue;
// 127 milliseconds to get answer on X/Y commands!!!
int64_t ans;
int ctr = 0;
if(SSgetint(CMD_MOTX, &ans)){
pthread_mutex_lock(&datamutex);
mountdata.motXposition.t = tgot;
mountdata.motXposition.val = X_MOT2RAD(ans);
pthread_mutex_unlock(&datamutex);
++ctr;
}
tgot = nanotime();
if(SSgetint(CMD_MOTY, &ans)){
pthread_mutex_lock(&datamutex);
mountdata.motXposition.t = tgot;
mountdata.motXposition.val = X_MOT2RAD(ans);
pthread_mutex_unlock(&datamutex);
++ctr;
}
if(ctr == 2){
mountdata.millis = (uint32_t)(1e3 * tgot);
DBG("Got both coords; millis=%d", mountdata.millis);
}
#endif
// 80 milliseconds to get answer on GETSTAT // 80 milliseconds to get answer on GETSTAT
if(!MountWriteRead(cmd_getstat, &d) || d.len != sizeof(SSstat)){ if(!MountWriteRead(cmd_getstat, &d) || d.len != sizeof(SSstat)){
#ifdef EBUG #ifdef EBUG
@@ -498,14 +563,13 @@ static void *mountthread(void _U_ *u){
errctr = 0; errctr = 0;
pthread_mutex_lock(&datamutex); pthread_mutex_lock(&datamutex);
// now change data // now change data
SSconvstat(status, &mountdata, t0); SSconvstat(status, &mountdata, &tcur);
pthread_mutex_unlock(&datamutex); pthread_mutex_unlock(&datamutex);
//DBG("GOT FULL stat by %g", nanotime() - t0);
// allow writing & getters // allow writing & getters
do{ do{
usleep(500); usleep(500);
}while(nanotime() - t0 < Conf.MountReqInterval); }while(timefromstart() - t0 < Conf.MountReqInterval);
t0 = nanotime(); t0 = timefromstart();
} }
data_free(&cmd_getstat); data_free(&cmd_getstat);
failed: failed:
@@ -521,8 +585,15 @@ static int ttyopen(const char *path, speed_t speed){
int fd = -1; int fd = -1;
struct termios2 tty; struct termios2 tty;
DBG("Try to open %s @ %d", path, speed); DBG("Try to open %s @ %d", path, speed);
if((fd = open(path, O_RDWR|O_NOCTTY)) < 0) return -1; if((fd = open(path, O_RDWR|O_NOCTTY)) < 0){
if(ioctl(fd, TCGETS2, &tty)){ close(fd); return -1; } DBG("Can't open device %s: %s", path, strerror(errno));
return -1;
}
if(ioctl(fd, TCGETS2, &tty)){
DBG("Can't read TTY settings");
close(fd);
return -1;
}
tty.c_lflag = 0; // ~(ICANON | ECHO | ECHOE | ISIG) tty.c_lflag = 0; // ~(ICANON | ECHO | ECHOE | ISIG)
tty.c_iflag = 0; // don't do any changes in input stream tty.c_iflag = 0; // don't do any changes in input stream
tty.c_oflag = 0; // don't do any changes in output stream tty.c_oflag = 0; // don't do any changes in output stream
@@ -531,7 +602,11 @@ static int ttyopen(const char *path, speed_t speed){
tty.c_ospeed = speed; tty.c_ospeed = speed;
//tty.c_cc[VMIN] = 0; // non-canonical mode //tty.c_cc[VMIN] = 0; // non-canonical mode
//tty.c_cc[VTIME] = 5; //tty.c_cc[VTIME] = 5;
if(ioctl(fd, TCSETS2, &tty)){ close(fd); return -1; } if(ioctl(fd, TCSETS2, &tty)){
DBG("Can't set TTY settings");
close(fd);
return -1;
}
DBG("Check speed: i=%d, o=%d", tty.c_ispeed, tty.c_ospeed); DBG("Check speed: i=%d, o=%d", tty.c_ispeed, tty.c_ospeed);
if(tty.c_ispeed != (speed_t) speed || tty.c_ospeed != (speed_t)speed){ close(fd); return -1; } if(tty.c_ispeed != (speed_t) speed || tty.c_ospeed != (speed_t)speed){ close(fd); return -1; }
// try to set exclusive // try to set exclusive
@@ -564,7 +639,7 @@ int openEncoder(){
if(encfd[i] < 0) return FALSE; if(encfd[i] < 0) return FALSE;
} }
encRtmout.tv_sec = 0; encRtmout.tv_sec = 0;
encRtmout.tv_usec = 1000; // 1ms encRtmout.tv_usec = 100000000 / Conf.EncoderDevSpeed;
if(pthread_create(&encthread, NULL, encoderthread2, NULL)){ if(pthread_create(&encthread, NULL, encoderthread2, NULL)){
for(int i = 0; i < 2; ++i){ for(int i = 0; i < 2; ++i){
close(encfd[i]); close(encfd[i]);
@@ -611,6 +686,7 @@ create_thread:
// close all opened serial devices and quit threads // close all opened serial devices and quit threads
void closeSerial(){ void closeSerial(){
// TODO: close devices in "model" mode too!
if(Conf.RunModel) return; if(Conf.RunModel) return;
if(mntfd > -1){ if(mntfd > -1){
DBG("Cancel mount thread"); DBG("Cancel mount thread");
@@ -642,6 +718,8 @@ mcc_errcodes_t getMD(mountdata_t *d){
pthread_mutex_lock(&datamutex); pthread_mutex_lock(&datamutex);
*d = mountdata; *d = mountdata;
pthread_mutex_unlock(&datamutex); pthread_mutex_unlock(&datamutex);
//DBG("ENCpos: %.10g/%.10g", d->encXposition.val, d->encYposition.val);
//DBG("millis: %u, encxt: %zd (time: %zd)", d->millis, d->encXposition.t.tv_sec, time(NULL));
return MCC_E_OK; return MCC_E_OK;
} }
@@ -660,30 +738,24 @@ static int wr(const data_t *out, data_t *in, int needeol){
return FALSE; return FALSE;
} }
clrmntbuf(); clrmntbuf();
//double t0 = nanotime();
if(out){ if(out){
if(out->len != (size_t)write(mntfd, out->buf, out->len)){ if(out->len != (size_t)write(mntfd, out->buf, out->len)){
DBG("written bytes not equal to need"); DBG("written bytes not equal to need");
return FALSE; return FALSE;
} }
//DBG("Send to mount %zd bytes: %s", out->len, out->buf);
if(needeol){ if(needeol){
int g = write(mntfd, "\r", 1); // add EOL int g = write(mntfd, "\r", 1); // add EOL
(void) g; (void) g;
} }
usleep(50000); // add little pause so that the idiot has time to swallow
} }
//DBG("sent by %g", nanotime() - t0);
//uint8_t buf[256];
//data_t dumb = {.buf = buf, .maxlen = 256};
if(!in) return TRUE; if(!in) return TRUE;
//if(!in) in = &dumb; // even if user don't ask for answer, try to read to clear trash
in->len = 0; in->len = 0;
for(size_t i = 0; i < in->maxlen; ++i){ for(size_t i = 0; i < in->maxlen; ++i){
int b = getmntbyte(); int b = getmntbyte();
if(b < 0) break; // nothing to read -> go out if(b < 0) break; // nothing to read -> go out
in->buf[in->len++] = (uint8_t) b; in->buf[in->len++] = (uint8_t) b;
} }
//DBG("got %zd bytes by %g", in->len, nanotime() - t0);
while(getmntbyte() > -1); while(getmntbyte() > -1);
return TRUE; return TRUE;
} }
@@ -787,16 +859,23 @@ int cmdC(SSconfig *conf, int rw){
}else{ // read }else{ // read
data_t d; data_t d;
d.buf = (uint8_t *) conf; d.buf = (uint8_t *) conf;
d.len = 0; d.maxlen = sizeof(SSconfig); d.len = 0; d.maxlen = 0;
ret = wr(rcmd, &d, 1);
DBG("write command: %s", ret ? "TRUE" : "FALSE");
if(!ret) goto rtn;
// make a huge pause for stupid SSII
usleep(100000);
d.len = 0; d.maxlen = sizeof(SSconfig);
ret = wr(rcmd, &d, 1); ret = wr(rcmd, &d, 1);
DBG("wr returned %s; got %zd bytes of %zd", ret ? "TRUE" : "FALSE", d.len, d.maxlen); DBG("wr returned %s; got %zd bytes of %zd", ret ? "TRUE" : "FALSE", d.len, d.maxlen);
if(d.len != d.maxlen) return FALSE; if(d.len != d.maxlen){ ret = FALSE; goto rtn; }
// simplest checksum // simplest checksum
uint16_t sum = 0; uint16_t sum = 0;
for(uint32_t i = 0; i < sizeof(SSconfig)-2; ++i) sum += d.buf[i]; for(uint32_t i = 0; i < sizeof(SSconfig)-2; ++i) sum += d.buf[i];
if(sum != conf->checksum){ if(sum != conf->checksum){
DBG("got sum: %u, need: %u", conf->checksum, sum); DBG("got sum: %u, need: %u", conf->checksum, sum);
return FALSE; ret = FALSE;
goto rtn;
} }
} }
rtn: rtn:

View File

@@ -32,38 +32,13 @@ extern "C"
#include <stdint.h> #include <stdint.h>
#include <sys/time.h> #include <sys/time.h>
// acceptable position error - 0.1'' // minimal serial speed of mount device
#define MCC_POSITION_ERROR (5e-7) #define MOUNT_BAUDRATE_MIN (1200)
// acceptable disagreement between motor and axis encoders - 2''
#define MCC_ENCODERS_ERROR (1e-7)
// max speeds (rad/s): xs=10 deg/s, ys=8 deg/s
#define MCC_MAX_X_SPEED (0.174533)
#define MCC_MAX_Y_SPEED (0.139626)
// accelerations by both axis (for model); TODO: move speeds/accelerations into config?
// xa=12.6 deg/s^2, ya= 9.5 deg/s^2
#define MCC_X_ACCELERATION (0.219911)
#define MCC_Y_ACCELERATION (0.165806)
// max speed interval, seconds // max speed interval, seconds
#define MCC_CONF_MAX_SPEEDINT (2.) #define MCC_CONF_MAX_SPEEDINT (2.)
// minimal speed interval in parts of EncoderReqInterval // minimal speed interval in parts of EncoderReqInterval
#define MCC_CONF_MIN_SPEEDC (3.) #define MCC_CONF_MIN_SPEEDC (3.)
// PID I cycle time (analog of "RC" for PID on opamps)
#define MCC_PID_CYCLE_TIME (5.)
// maximal PID refresh time interval (if larger all old data will be cleared)
#define MCC_PID_MAX_DT (1.)
// normal PID refresh interval
#define MCC_PID_REFRESH_DT (0.1)
// boundary conditions for axis state: "slewing/pointing/guiding"
// if angle < MCC_MAX_POINTING_ERR, change state from "slewing" to "pointing": 8 degrees
//#define MCC_MAX_POINTING_ERR (0.20943951)
//#define MCC_MAX_POINTING_ERR (0.08726646)
#define MCC_MAX_POINTING_ERR (0.13962634)
// if angle < MCC_MAX_GUIDING_ERR, chane state from "pointing" to "guiding": 1.5 deg
#define MCC_MAX_GUIDING_ERR (0.026179939)
// if error less than this value we suppose that target is captured and guiding is good: 0.1''
#define MCC_MAX_ATTARGET_ERR (4.8481368e-7)
// error codes // error codes
typedef enum{ typedef enum{
@@ -73,6 +48,7 @@ typedef enum{
MCC_E_ENCODERDEV, // encoder device error or can't open MCC_E_ENCODERDEV, // encoder device error or can't open
MCC_E_MOUNTDEV, // mount device error or can't open MCC_E_MOUNTDEV, // mount device error or can't open
MCC_E_FAILED, // failed to run command - protocol error MCC_E_FAILED, // failed to run command - protocol error
MCC_E_AMOUNT // Just amount of errors
} mcc_errcodes_t; } mcc_errcodes_t;
typedef struct{ typedef struct{
@@ -87,14 +63,23 @@ typedef struct{
int SepEncoder; // ==1 if encoder works as separate serial device, ==2 if there's new version with two devices int SepEncoder; // ==1 if encoder works as separate serial device, ==2 if there's new version with two devices
char* EncoderXDevPath; // paths to new controller devices char* EncoderXDevPath; // paths to new controller devices
char* EncoderYDevPath; char* EncoderYDevPath;
double EncodersDisagreement; // acceptable disagreement between motor and axis encoders
double MountReqInterval; // interval between subsequent mount requests (seconds) double MountReqInterval; // interval between subsequent mount requests (seconds)
double EncoderReqInterval; // interval between subsequent encoder requests (seconds) double EncoderReqInterval; // interval between subsequent encoder requests (seconds)
double EncoderSpeedInterval; // interval between speed calculations double EncoderSpeedInterval; // interval between speed calculations
int RunModel; // == 1 if you want to use model instead of real mount int RunModel; // == 1 if you want to use model instead of real mount
double PIDMaxDt; // maximal PID refresh time interval (if larger all old data will be cleared)
double PIDRefreshDt; // normal PID refresh interval
double PIDCycleDt; // PID I cycle time (analog of "RC" for PID on opamps)
PIDpar_t XPIDC; // gain parameters of PID for both axiss (C - coordinate driven, V - velocity driven) PIDpar_t XPIDC; // gain parameters of PID for both axiss (C - coordinate driven, V - velocity driven)
PIDpar_t XPIDV; PIDpar_t XPIDV;
PIDpar_t YPIDC; PIDpar_t YPIDC;
PIDpar_t YPIDV; PIDpar_t YPIDV;
double MaxPointingErr; // if angle < this, change state from "slewing" to "pointing" (coarse pointing): 8 degrees
double MaxFinePointingErr; // if angle < this, chane state from "pointing" to "guiding" (fine poinging): 1.5 deg
double MaxGuidingErr; // if error less than this value we suppose that target is captured and guiding is good (true guiding): 0.1''
int XEncZero; // encoders' zero position
int YEncZero;
} conf_t; } conf_t;
// coordinates/speeds in degrees or d/s: X, Y // coordinates/speeds in degrees or d/s: X, Y
@@ -105,7 +90,7 @@ typedef struct{
// coordinate/speed and time of last measurement // coordinate/speed and time of last measurement
typedef struct{ typedef struct{
double val; double val;
double t; struct timespec t;
} coordval_t; } coordval_t;
typedef struct{ typedef struct{
@@ -206,6 +191,9 @@ typedef struct{
double outplimit; // Output Limit, percent (0..100) double outplimit; // Output Limit, percent (0..100)
double currlimit; // Current Limit (A) double currlimit; // Current Limit (A)
double intlimit; // Integral Limit (???) double intlimit; // Integral Limit (???)
// these params are taken from mount by text commands (don't save negative values - better save these marks in xybits
double motor_stepsperrev;// encoder's steps per revolution: motor and axis
double axis_stepsperrev; // negative sign of these values means reverse direction
} __attribute__((packed)) axis_config_t; } __attribute__((packed)) axis_config_t;
// hardware configuration // hardware configuration
@@ -247,7 +235,7 @@ typedef struct{
void (*quit)(); // deinit void (*quit)(); // deinit
mcc_errcodes_t (*getMountData)(mountdata_t *d); // get last data mcc_errcodes_t (*getMountData)(mountdata_t *d); // get last data
// mcc_errcodes_t (*slewTo)(const coordpair_t *target, slewflags_t flags); // mcc_errcodes_t (*slewTo)(const coordpair_t *target, slewflags_t flags);
mcc_errcodes_t (*correctTo)(const coordval_pair_t *target, const coordpair_t *endpoint); mcc_errcodes_t (*correctTo)(const coordval_pair_t *target);
mcc_errcodes_t (*moveTo)(const coordpair_t *target); // move to given position and stop mcc_errcodes_t (*moveTo)(const coordpair_t *target); // move to given position and stop
mcc_errcodes_t (*moveWspeed)(const coordpair_t *target, const coordpair_t *speed); // move with given max speed mcc_errcodes_t (*moveWspeed)(const coordpair_t *target, const coordpair_t *speed); // move with given max speed
mcc_errcodes_t (*setSpeed)(const coordpair_t *tagspeed); // set speed mcc_errcodes_t (*setSpeed)(const coordpair_t *tagspeed); // set speed
@@ -257,7 +245,13 @@ typedef struct{
mcc_errcodes_t (*longCmd)(long_command_t *cmd); // send/get long command mcc_errcodes_t (*longCmd)(long_command_t *cmd); // send/get long command
mcc_errcodes_t (*getHWconfig)(hardware_configuration_t *c); // get hardware configuration mcc_errcodes_t (*getHWconfig)(hardware_configuration_t *c); // get hardware configuration
mcc_errcodes_t (*saveHWconfig)(hardware_configuration_t *c); // save hardware configuration mcc_errcodes_t (*saveHWconfig)(hardware_configuration_t *c); // save hardware configuration
double (*currentT)(); // current time int (*currentT)(struct timespec *t); // current time
double (*timeFromStart)(); // amount of seconds from last init
double (*timeDiff)(const struct timespec *time1, const struct timespec *time0); // difference of times
double (*timeDiff0)(const struct timespec *time1); // difference between current time and last init time
mcc_errcodes_t (*getMaxSpeed)(coordpair_t *v); // maximal speed by both axis
mcc_errcodes_t (*getMinSpeed)(coordpair_t *v); // minimal -//-
mcc_errcodes_t (*getAcceleration)(coordpair_t *a); // acceleration/deceleration
} mount_t; } mount_t;
extern mount_t Mount; extern mount_t Mount;

View File

@@ -26,6 +26,13 @@
#include "serial.h" #include "serial.h"
#include "ssii.h" #include "ssii.h"
int X_ENC_ZERO = 0, Y_ENC_ZERO = 0;
// defaults until read from controller
double X_MOT_STEPSPERREV = 13312000.,
Y_MOT_STEPSPERREV = 17578668.,
X_ENC_STEPSPERREV = 67108864.,
Y_ENC_STEPSPERREV = 67108864.;
uint16_t SScalcChecksum(uint8_t *buf, int len){ uint16_t SScalcChecksum(uint8_t *buf, int len){
uint16_t checksum = 0; uint16_t checksum = 0;
for(int i = 0; i < len; i++){ for(int i = 0; i < len; i++){
@@ -67,17 +74,18 @@ static void ChkStopped(const SSstat *s, mountdata_t *m){
* @param m (o) - output * @param m (o) - output
* @param t - measurement time * @param t - measurement time
*/ */
void SSconvstat(const SSstat *s, mountdata_t *m, double t){ void SSconvstat(const SSstat *s, mountdata_t *m, struct timespec *t){
if(!s || !m) return; if(!s || !m || !t) return;
m->motXposition.val = X_MOT2RAD(s->Xmot); m->motXposition.val = X_MOT2RAD(s->Xmot);
m->motYposition.val = Y_MOT2RAD(s->Ymot); m->motYposition.val = Y_MOT2RAD(s->Ymot);
ChkStopped(s, m); ChkStopped(s, m);
m->motXposition.t = m->motYposition.t = t; m->motXposition.t = m->motYposition.t = *t;
// fill encoder data from here, as there's no separate enc thread // fill encoder data from here, as there's no separate enc thread
if(!Conf.SepEncoder){ if(!Conf.SepEncoder){
m->encXposition.val = X_ENC2RAD(s->Xenc); m->encXposition.val = Xenc2rad(s->Xenc);
m->encYposition.val = Y_ENC2RAD(s->Yenc); DBG("encx: %g", m->encXposition.val);
m->encXposition.t = m->encYposition.t = t; m->encYposition.val = Yenc2rad(s->Yenc);
m->encXposition.t = m->encYposition.t = *t;
getXspeed(); getYspeed(); getXspeed(); getYspeed();
} }
m->keypad = s->keypad; m->keypad = s->keypad;
@@ -176,33 +184,39 @@ int SSstop(int emerg){
mcc_errcodes_t updateMotorPos(){ mcc_errcodes_t updateMotorPos(){
mountdata_t md = {0}; mountdata_t md = {0};
if(Conf.RunModel) return MCC_E_OK; if(Conf.RunModel) return MCC_E_OK;
double t0 = nanotime(), t = 0.; double t0 = timefromstart(), t = 0.;
struct timespec curt;
DBG("start @ %g", t0); DBG("start @ %g", t0);
do{ do{
t = nanotime(); t = timefromstart();
if(!curtime(&curt)){
usleep(10000);
continue;
}
//DBG("XENC2RAD: %g (xez=%d, xesr=%.10g)", Xenc2rad(32424842), X_ENC_ZERO, X_ENC_STEPSPERREV);
if(MCC_E_OK == getMD(&md)){ if(MCC_E_OK == getMD(&md)){
if(md.encXposition.t == 0 || md.encYposition.t == 0){ if(md.encXposition.t.tv_sec == 0 || md.encYposition.t.tv_sec == 0){
DBG("Just started, t-t0 = %g!", t - t0); DBG("Just started? t-t0 = %g!", t - t0);
sleep(1); usleep(10000);
DBG("t-t0 = %g", nanotime() - t0);
//usleep(10000);
continue; continue;
} }
DBG("got; t pos x/y: %g/%g; tnow: %g", md.encXposition.t, md.encYposition.t, t); if(md.Xstate != AXIS_STOPPED || md.Ystate != AXIS_STOPPED) return MCC_E_OK;
DBG("got; t pos x/y: %ld/%ld; tnow: %ld", md.encXposition.t.tv_sec, md.encYposition.t.tv_sec, curt.tv_sec);
mcc_errcodes_t OK = MCC_E_OK; mcc_errcodes_t OK = MCC_E_OK;
if(fabs(md.motXposition.val - md.encXposition.val) > MCC_ENCODERS_ERROR && md.Xstate == AXIS_STOPPED){ if(fabs(md.motXposition.val - md.encXposition.val) > Conf.EncodersDisagreement && md.Xstate == AXIS_STOPPED){
DBG("NEED to sync X: motors=%g, axiss=%g", md.motXposition.val, md.encXposition.val); DBG("NEED to sync X: motors=%g, axis=%g", md.motXposition.val, md.encXposition.val);
DBG("new motsteps: %d", X_RAD2MOT(md.encXposition.val));
if(!SSsetterI(CMD_MOTXSET, X_RAD2MOT(md.encXposition.val))){ if(!SSsetterI(CMD_MOTXSET, X_RAD2MOT(md.encXposition.val))){
DBG("Xpos sync failed!"); DBG("Xpos sync failed!");
OK = MCC_E_FAILED; OK = MCC_E_FAILED;
}else DBG("Xpos sync OK, Dt=%g", nanotime() - t0); }else DBG("Xpos sync OK, Dt=%g", t - t0);
} }
if(fabs(md.motYposition.val - md.encYposition.val) > MCC_ENCODERS_ERROR && md.Xstate == AXIS_STOPPED){ if(fabs(md.motYposition.val - md.encYposition.val) > Conf.EncodersDisagreement && md.Ystate == AXIS_STOPPED){
DBG("NEED to sync Y: motors=%g, axiss=%g", md.motYposition.val, md.encYposition.val); DBG("NEED to sync Y: motors=%g, axis=%g", md.motYposition.val, md.encYposition.val);
if(!SSsetterI(CMD_MOTYSET, Y_RAD2MOT(md.encYposition.val))){ if(!SSsetterI(CMD_MOTYSET, Y_RAD2MOT(md.encYposition.val))){
DBG("Ypos sync failed!"); DBG("Ypos sync failed!");
OK = MCC_E_FAILED; OK = MCC_E_FAILED;
}else DBG("Ypos sync OK, Dt=%g", nanotime() - t0); }else DBG("Ypos sync OK, Dt=%g", t - t0);
} }
if(MCC_E_OK == OK){ if(MCC_E_OK == OK){
DBG("Encoders synced"); DBG("Encoders synced");

View File

@@ -173,64 +173,74 @@
#define SITECH_LOOP_FREQUENCY (1953.) #define SITECH_LOOP_FREQUENCY (1953.)
// amount of consequent same coordinates to detect stop // amount of consequent same coordinates to detect stop
#define MOTOR_STOPPED_CNT (4) #define MOTOR_STOPPED_CNT (19)
// replace macros with global variables inited when config read
extern int X_ENC_ZERO, Y_ENC_ZERO;
extern double X_MOT_STEPSPERREV, Y_MOT_STEPSPERREV, X_ENC_STEPSPERREV, Y_ENC_STEPSPERREV;
// TODO: take it from settings? // TODO: take it from settings?
// steps per revolution (SSI - x4 - for SSI) // steps per revolution (SSI - x4 - for SSI)
#define X_MOT_STEPSPERREV_SSI (13312000.) // -> hwconf.Xconf.mot/enc_stepsperrev
//#define X_MOT_STEPSPERREV_SSI (13312000.)
// 13312000 / 4 = 3328000 // 13312000 / 4 = 3328000
#define X_MOT_STEPSPERREV (3328000.) //#define X_MOT_STEPSPERREV (3328000.)
#define Y_MOT_STEPSPERREV_SSI (17578668.) //#define Y_MOT_STEPSPERREV_SSI (17578668.)
// 17578668 / 4 = 4394667 // 17578668 / 4 = 4394667
#define Y_MOT_STEPSPERREV (4394667.) //#define Y_MOT_STEPSPERREV (4394667.)
// encoder per revolution // encoder per revolution
#define X_ENC_STEPSPERREV (67108864.) //#define X_ENC_STEPSPERREV (67108864.)
#define Y_ENC_STEPSPERREV (67108864.) //#define Y_ENC_STEPSPERREV (67108864.)
// encoder zero position // encoder zero position
#define X_ENC_ZERO (61245239) // -> conf.XEncZero/YEncZero
#define Y_ENC_ZERO (36999830) //#define X_ENC_ZERO (61245239)
// encoder reversed (no: +1) //#define Y_ENC_ZERO (36999830)
#define X_ENC_SIGN (-1.) // encoder reversed (no: +1) -> sign of ...stepsperrev
#define Y_ENC_SIGN (-1.) //#define X_ENC_SIGN (-1.)
//#define Y_ENC_SIGN (-1.)
// encoder position to radians and back // encoder position to radians and back
#define X_ENC2RAD(n) ang2half(X_ENC_SIGN * 2.*M_PI * ((double)((n)-X_ENC_ZERO)) / X_ENC_STEPSPERREV) #define Xenc2rad(n) ang2half(2.*M_PI * ((double)((n)-(X_ENC_ZERO))) / (X_ENC_STEPSPERREV))
#define Y_ENC2RAD(n) ang2half(Y_ENC_SIGN * 2.*M_PI * ((double)((n)-Y_ENC_ZERO)) / Y_ENC_STEPSPERREV) #define Yenc2rad(n) ang2half(2.*M_PI * ((double)((n)-(Y_ENC_ZERO))) / (Y_ENC_STEPSPERREV))
#define X_RAD2ENC(r) ((uint32_t)((r) / 2./M_PI * X_ENC_STEPSPERREV)) #define Xrad2enc(r) ((uint32_t)((r) / 2./M_PI * (X_ENC_STEPSPERREV)))
#define Y_RAD2ENC(r) ((uint32_t)((r) / 2./M_PI * Y_ENC_STEPSPERREV)) #define Yrad2enc(r) ((uint32_t)((r) / 2./M_PI * (Y_ENC_STEPSPERREV)))
// convert angle in radians to +-pi // convert angle in radians to +-pi
static inline double ang2half(double ang){ static inline __attribute__((always_inline)) double ang2half(double ang){
ang = fmod(ang, 2.*M_PI);
if(ang < -M_PI) ang += 2.*M_PI; if(ang < -M_PI) ang += 2.*M_PI;
else if(ang > M_PI) ang -= 2.*M_PI; else if(ang > M_PI) ang -= 2.*M_PI;
return ang; return ang;
} }
// convert to only positive: 0..2pi // convert to only positive: 0..2pi
static inline double ang2full(double ang){ static inline __attribute__((always_inline)) double ang2full(double ang){
ang = fmod(ang, 2.*M_PI);
if(ang < 0.) ang += 2.*M_PI; if(ang < 0.) ang += 2.*M_PI;
else if(ang > 2.*M_PI) ang -= 2.*M_PI; else if(ang > 2.*M_PI) ang -= 2.*M_PI;
return ang; return ang;
} }
// motor position to radians and back // motor position to radians and back
#define X_MOT2RAD(n) ang2half(2. * M_PI * ((double)(n)) / X_MOT_STEPSPERREV) #define X_MOT2RAD(n) ang2half(2. * M_PI * ((double)(n)) / (X_MOT_STEPSPERREV))
#define Y_MOT2RAD(n) ang2half(2. * M_PI * ((double)(n)) / Y_MOT_STEPSPERREV) #define Y_MOT2RAD(n) ang2half(2. * M_PI * ((double)(n)) / (Y_MOT_STEPSPERREV))
#define X_RAD2MOT(r) ((int32_t)((r) / (2. * M_PI) * X_MOT_STEPSPERREV)) #define X_RAD2MOT(r) ((int32_t)((r) / (2. * M_PI) * (X_MOT_STEPSPERREV)))
#define Y_RAD2MOT(r) ((int32_t)((r) / (2. * M_PI) * Y_MOT_STEPSPERREV)) #define Y_RAD2MOT(r) ((int32_t)((r) / (2. * M_PI) * (Y_MOT_STEPSPERREV)))
// motor speed in rad/s and back // motor speed in rad/s and back
#define X_MOTSPD2RS(n) (X_MOT2RAD(n) / 65536. * SITECH_LOOP_FREQUENCY) #define X_MOTSPD2RS(n) (X_MOT2RAD(n) / 65536. * (SITECH_LOOP_FREQUENCY))
#define Y_MOTSPD2RS(n) (Y_MOT2RAD(n) / 65536. * SITECH_LOOP_FREQUENCY) #define Y_MOTSPD2RS(n) (Y_MOT2RAD(n) / 65536. * (SITECH_LOOP_FREQUENCY))
#define X_RS2MOTSPD(r) ((int32_t)(X_RAD2MOT(r) * 65536. / SITECH_LOOP_FREQUENCY)) #define X_RS2MOTSPD(r) ((int32_t)(X_RAD2MOT(r) * 65536. / (SITECH_LOOP_FREQUENCY)))
#define Y_RS2MOTSPD(r) ((int32_t)(Y_RAD2MOT(r) * 65536. / SITECH_LOOP_FREQUENCY)) #define Y_RS2MOTSPD(r) ((int32_t)(Y_RAD2MOT(r) * 65536. / (SITECH_LOOP_FREQUENCY)))
// motor acceleration -//- // motor acceleration -//-
#define X_MOTACC2RS(n) (X_MOT2RAD(n) / 65536. * SITECH_LOOP_FREQUENCY * SITECH_LOOP_FREQUENCY) #define X_MOTACC2RS(n) (X_MOT2RAD(n) / 65536. * (SITECH_LOOP_FREQUENCY) * (SITECH_LOOP_FREQUENCY))
#define Y_MOTACC2RS(n) (Y_MOT2RAD(n) / 65536. * SITECH_LOOP_FREQUENCY * SITECH_LOOP_FREQUENCY) #define Y_MOTACC2RS(n) (Y_MOT2RAD(n) / 65536. * (SITECH_LOOP_FREQUENCY) * (SITECH_LOOP_FREQUENCY))
#define X_RS2MOTACC(r) ((int32_t)(X_RAD2MOT(r) * 65536. / SITECH_LOOP_FREQUENCY / SITECH_LOOP_FREQUENCY)) #define X_RS2MOTACC(r) ((int32_t)(X_RAD2MOT(r) * 65536. / (SITECH_LOOP_FREQUENCY) / (SITECH_LOOP_FREQUENCY)))
#define Y_RS2MOTACC(r) ((int32_t)(Y_RAD2MOT(r) * 65536. / SITECH_LOOP_FREQUENCY / SITECH_LOOP_FREQUENCY)) #define Y_RS2MOTACC(r) ((int32_t)(Y_RAD2MOT(r) * 65536. / (SITECH_LOOP_FREQUENCY) / (SITECH_LOOP_FREQUENCY)))
// adder time to seconds vice versa // adder time to seconds vice versa
#define ADDER2S(a) ((a) / SITECH_LOOP_FREQUENCY) #define ADDER2S(a) ((a) / (SITECH_LOOP_FREQUENCY))
#define S2ADDER(s) ((s) * SITECH_LOOP_FREQUENCY) #define S2ADDER(s) ((s) * (SITECH_LOOP_FREQUENCY))
// encoder's tolerance (ticks) // encoder's tolerance (ticks)
#define YencTOL (25.) #define YencTOL (25.)
@@ -331,7 +341,7 @@ typedef struct{
} __attribute__((packed)) SSconfig; } __attribute__((packed)) SSconfig;
uint16_t SScalcChecksum(uint8_t *buf, int len); uint16_t SScalcChecksum(uint8_t *buf, int len);
void SSconvstat(const SSstat *status, mountdata_t *mountdata, double t); void SSconvstat(const SSstat *status, mountdata_t *mountdata, struct timespec *t);
int SStextcmd(const char *cmd, data_t *answer); int SStextcmd(const char *cmd, data_t *answer);
int SSrawcmd(const char *cmd, data_t *answer); int SSrawcmd(const char *cmd, data_t *answer);
int SSgetint(const char *cmd, int64_t *ans); int SSgetint(const char *cmd, int64_t *ans);

View File

@@ -8,7 +8,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
find_package(ASIO QUIET CONFIG) # find_package(ASIO QUIET CONFIG)
find_package(ASIO QUIET)
if (ASIO_FOUND) if (ASIO_FOUND)
message(STATUS "ASIO library was found in the host system") message(STATUS "ASIO library was found in the host system")
else() else()

View File

@@ -13,6 +13,7 @@
#include "mcc_ccte_erfa.h" #include "mcc_ccte_erfa.h"
#include "mcc_slewing_model.h" #include "mcc_slewing_model.h"
#include "mcc_tracking_model.h"
namespace asibfm700 namespace asibfm700
{ {
@@ -24,5 +25,6 @@ typedef mcc::MccDefaultPCM<asibfm700MountType> Asibfm700PCM;
typedef mcc::MccPZoneContainer<mcc::MccTimeDuration> Asibfm700PZoneContainer; typedef mcc::MccPZoneContainer<mcc::MccTimeDuration> Asibfm700PZoneContainer;
typedef mcc::utils::MccSpdlogLogger Asibfm700Logger; typedef mcc::utils::MccSpdlogLogger Asibfm700Logger;
typedef mcc::MccSimpleSlewingModel Asibfm700SlewingModel; typedef mcc::MccSimpleSlewingModel Asibfm700SlewingModel;
typedef mcc::MccSimpleTrackingModel Asibfm700TrackingModel;
} // namespace asibfm700 } // namespace asibfm700

View File

@@ -139,6 +139,10 @@ static auto Asibfm700MountConfigDefaults = std::make_tuple(
std::chrono::milliseconds(100), std::chrono::milliseconds(100),
{"telemetry request interval (in millisecs) in slewing mode"}}, {"telemetry request interval (in millisecs) in slewing mode"}},
simple_config_record_t{"slewingPathFilename",
std::string(),
{"slewing trajectory filename", "if it is an empty - just skip saving"}},
// target-mount coordinate difference in arcsecs to start adjusting of slewing // target-mount coordinate difference in arcsecs to start adjusting of slewing
simple_config_record_t{"adjustCoordDiff", simple_config_record_t{"adjustCoordDiff",
50.0, 50.0,
@@ -176,6 +180,10 @@ static auto Asibfm700MountConfigDefaults = std::make_tuple(
{"maximal valid target-to-mount distance for tracking process (arcsecs)", {"maximal valid target-to-mount distance for tracking process (arcsecs)",
"if current distance is greater than assume current mount coordinate as target point"}}, "if current distance is greater than assume current mount coordinate as target point"}},
simple_config_record_t{"trackingPathFilename",
std::string(),
{"tracking trajectory filename", "if it is an empty - just skip saving"}},
/* prohibited zones */ /* prohibited zones */
@@ -183,10 +191,10 @@ static auto Asibfm700MountConfigDefaults = std::make_tuple(
simple_config_record_t{"pzMinAltitude", mcc::MccAngle(10.0_degs), {"minimal altitude"}}, simple_config_record_t{"pzMinAltitude", mcc::MccAngle(10.0_degs), {"minimal altitude"}},
// HA-axis limit switch minimal value // HA-axis limit switch minimal value
simple_config_record_t{"pzLimitSwitchHAMin", mcc::MccAngle(-170.0_degs), {"HA-axis limit switch minimal value"}}, simple_config_record_t{"pzLimitSwitchHAMin", mcc::MccAngle(-270.0_degs), {"HA-axis limit switch minimal value"}},
// HA-axis limit switch maximal value // HA-axis limit switch maximal value
simple_config_record_t{"pzLimitSwitchHAMax", mcc::MccAngle(170.0_degs), {"HA-axis limit switch maximal value"}}, simple_config_record_t{"pzLimitSwitchHAMax", mcc::MccAngle(270.0_degs), {"HA-axis limit switch maximal value"}},
// DEC-axis limit switch minimal value // DEC-axis limit switch minimal value
simple_config_record_t{"pzLimitSwitchDecMin", mcc::MccAngle(-90.0_degs), {"DEC-axis limit switch minimal value"}}, simple_config_record_t{"pzLimitSwitchDecMin", mcc::MccAngle(-90.0_degs), {"DEC-axis limit switch minimal value"}},
@@ -235,20 +243,35 @@ static auto Asibfm700MountConfigDefaults = std::make_tuple(
// mount axes rate calculation interval in millisecs // mount axes rate calculation interval in millisecs
simple_config_record_t{"EncoderSpeedInterval", simple_config_record_t{"EncoderSpeedInterval",
std::chrono::milliseconds(100), std::chrono::milliseconds(50),
{"mount axes rate calculation interval in millisecs"}}, {"mount axes rate calculation interval in millisecs"}},
simple_config_record_t{"PIDMaxDt",
std::chrono::milliseconds(1000),
{"maximal PID refresh time interval in millisecs",
"NOTE: if PID data will be refreshed with interval longer than this value (e.g. user polls "
"encoder data too rarely)",
"then the PID 'expired' data will be cleared and new computing loop is started"}},
simple_config_record_t{"PIDRefreshDt", std::chrono::milliseconds(100), {"PID refresh interval"}},
simple_config_record_t{"PIDCycleDt",
std::chrono::milliseconds(5000),
{"PID I cycle time (analog of 'RC' for PID on opamps)"}},
// X-axis coordinate PID P,I,D-params // X-axis coordinate PID P,I,D-params
simple_config_record_t{"XPIDC", std::vector<double>{0.8, 0.1, 0.3}, {"X-axis coordinate PID P,I,D-params"}}, simple_config_record_t{"XPIDC", std::vector<double>{0.5, 0.1, 0.2}, {"X-axis coordinate PID P,I,D-params"}},
// X-axis rate PID P,I,D-params // X-axis rate PID P,I,D-params
simple_config_record_t{"XPIDV", std::vector<double>{1.0, 0.01, 0.2}, {"X-axis rate PID P,I,D-params"}}, simple_config_record_t{"XPIDV", std::vector<double>{0.09, 0.0, 0.05}, {"X-axis rate PID P,I,D-params"}},
// Y-axis coordinate PID P, I, D-params // Y-axis coordinate PID P, I, D-params
simple_config_record_t{"YPIDC", std::vector<double>{0.8, 0.1, 0.3}, {"Y-axis coordinate PID P, I, D-params"}}, simple_config_record_t{"YPIDC", std::vector<double>{0.5, 0.1, 0.2}, {"Y-axis coordinate PID P, I, D-params"}},
// Y-axis rate PID P,I,D-params // Y-axis rate PID P,I,D-params
simple_config_record_t{"YPIDV", std::vector<double>{0.5, 0.2, 0.5}, {"Y-axis rate PID P,I,D-params"}}, simple_config_record_t{"YPIDV", std::vector<double>{0.09, 0.0, 0.05}, {"Y-axis rate PID P,I,D-params"}},
// maximal moving rate (degrees per second) along HA-axis (Y-axis of Sidereal servo microcontroller) // maximal moving rate (degrees per second) along HA-axis (Y-axis of Sidereal servo microcontroller)
@@ -261,7 +284,23 @@ static auto Asibfm700MountConfigDefaults = std::make_tuple(
simple_config_record_t{ simple_config_record_t{
"hwMaxRateDEC", "hwMaxRateDEC",
mcc::MccAngle(10.0_degs), mcc::MccAngle(10.0_degs),
{"maximal moving rate (degrees per second) along DEC-axis (X-axis of Sidereal servo microcontroller)"}} {"maximal moving rate (degrees per second) along DEC-axis (X-axis of Sidereal servo microcontroller)"}},
simple_config_record_t{"MaxPointingErr",
mcc::MccAngle(8.0_degs),
{"slewing-to-pointing mode angular limit in degrees"}},
simple_config_record_t{"MaxFinePointingErr",
mcc::MccAngle(1.5_degs),
{"pointing-to-guiding mode angular limit in degrees"}},
simple_config_record_t{"MaxGuidingErr",
mcc::MccAngle(0.5_arcsecs),
{"guiding 'good'-flag error cirle radius (mount-to-target distance) in degrees"}},
simple_config_record_t{"XEncZero", mcc::MccAngle(0.0_degs), {"X-axis encoder zero-point in degrees"}},
simple_config_record_t{"YEncZero", mcc::MccAngle(0.0_degs), {"Y-axis encoder zero-point in degrees"}}
); );
@@ -473,6 +512,16 @@ public:
secs = getValue<std::chrono::milliseconds>("EncoderSpeedInterval").value_or(std::chrono::milliseconds{}); secs = getValue<std::chrono::milliseconds>("EncoderSpeedInterval").value_or(std::chrono::milliseconds{});
hw_cfg.devConfig.EncoderSpeedInterval = secs.count(); hw_cfg.devConfig.EncoderSpeedInterval = secs.count();
secs = getValue<std::chrono::milliseconds>("PIDMaxDt").value_or(std::chrono::milliseconds{1000});
hw_cfg.devConfig.PIDMaxDt = secs.count();
secs = getValue<std::chrono::milliseconds>("PIDRefreshDt").value_or(std::chrono::milliseconds{100});
hw_cfg.devConfig.PIDRefreshDt = secs.count();
secs = getValue<std::chrono::milliseconds>("PIDCycleDt").value_or(std::chrono::milliseconds{5000});
hw_cfg.devConfig.PIDCycleDt = secs.count();
std::vector<double> pid = getValue<std::vector<double>>("XPIDC").value_or(std::vector<double>{}); std::vector<double> pid = getValue<std::vector<double>>("XPIDC").value_or(std::vector<double>{});
if (pid.size() > 2) { if (pid.size() > 2) {
hw_cfg.devConfig.XPIDC.P = pid[0]; hw_cfg.devConfig.XPIDC.P = pid[0];
@@ -501,6 +550,24 @@ public:
hw_cfg.devConfig.YPIDV.D = pid[2]; hw_cfg.devConfig.YPIDV.D = pid[2];
} }
double ang = getValue<mcc::MccAngle>("MaxPointingErr").value_or(mcc::MccAngle(8.0_degs));
hw_cfg.devConfig.MaxPointingErr = ang;
ang = getValue<mcc::MccAngle>("MaxFinePointingErr").value_or(mcc::MccAngle(1.5_degs));
hw_cfg.devConfig.MaxFinePointingErr = ang;
ang = getValue<mcc::MccAngle>("MaxGuidingErr").value_or(mcc::MccAngle(0.5_arcsecs));
hw_cfg.devConfig.MaxGuidingErr = ang;
ang = getValue<mcc::MccAngle>("XEncZero").value_or(mcc::MccAngle(0.0_degs));
hw_cfg.devConfig.XEncZero = ang;
ang = getValue<mcc::MccAngle>("YEncZero").value_or(mcc::MccAngle(0.0_degs));
hw_cfg.devConfig.YEncZero = ang;
return hw_cfg; return hw_cfg;
} }
@@ -540,6 +607,9 @@ public:
pars.slewTimeout = getValue<decltype(pars.slewTimeout)>("slewTimeout").value_or(pars.slewTimeout); pars.slewTimeout = getValue<decltype(pars.slewTimeout)>("slewTimeout").value_or(pars.slewTimeout);
pars.slewingPathFilename =
getValue<decltype(pars.slewingPathFilename)>("slewingPathFilename").value_or(std::string());
get_value("trackingTelemetryInterval", pars.trackingTelemetryInterval); get_value("trackingTelemetryInterval", pars.trackingTelemetryInterval);
pars.timeShiftToTargetPoint = getValue<decltype(pars.timeShiftToTargetPoint)>("timeShiftToTargetPoint") pars.timeShiftToTargetPoint = getValue<decltype(pars.timeShiftToTargetPoint)>("timeShiftToTargetPoint")
@@ -552,6 +622,9 @@ public:
getValue<decltype(pars.trackingMaxCoordDiff)>("trackingMaxCoordDiff").value_or(pars.trackingMaxCoordDiff) * getValue<decltype(pars.trackingMaxCoordDiff)>("trackingMaxCoordDiff").value_or(pars.trackingMaxCoordDiff) *
arcsecs2rad; arcsecs2rad;
pars.trackingPathFilename =
getValue<decltype(pars.trackingPathFilename)>("trackingPathFilename").value_or(std::string());
return pars; return pars;
} }
@@ -693,6 +766,14 @@ public:
val = getValue<std::string>("EncoderYDevPath").value_or(std::string{}); val = getValue<std::string>("EncoderYDevPath").value_or(std::string{});
fname = mcc::utils::trimSpaces(val); fname = mcc::utils::trimSpaces(val);
setValue("EncoderYDevPath", fname); setValue("EncoderYDevPath", fname);
val = getValue<std::string>("slewingPathFilename").value_or(std::string{});
fname = mcc::utils::trimSpaces(val);
setValue("slewingPathFilename", fname);
val = getValue<std::string>("trackingPathFilename").value_or(std::string{});
fname = mcc::utils::trimSpaces(val);
setValue("trackingPathFilename", fname);
} }
} catch (std::ios_base::failure const& ex) { } catch (std::ios_base::failure const& ex) {
ec = ex.code(); ec = ex.code();

View File

@@ -19,7 +19,7 @@ Asibfm700Mount::Asibfm700Mount(Asibfm700MountConfig const& config, std::shared_p
std::make_tuple(this), std::make_tuple(this),
std::make_tuple(), std::make_tuple(),
std::make_tuple(this, Asibfm700Logger{logger}), std::make_tuple(this, Asibfm700Logger{logger}),
std::make_tuple(this), std::make_tuple(this, Asibfm700Logger{logger}),
std::make_tuple(logger, Asibfm700Logger::LOGGER_DEFAULT_FORMAT)), std::make_tuple(logger, Asibfm700Logger::LOGGER_DEFAULT_FORMAT)),
// base_gm_class_t(Asibfm700StartState{}, // base_gm_class_t(Asibfm700StartState{},
// std::make_tuple(config.servoControllerConfig()), // std::make_tuple(config.servoControllerConfig()),
@@ -126,6 +126,9 @@ Asibfm700Mount::error_t Asibfm700Mount::initMount()
logInfo(" MountReqInterval: {}", to_msecs(hw_cfg.devConfig.MountReqInterval)); logInfo(" MountReqInterval: {}", to_msecs(hw_cfg.devConfig.MountReqInterval));
logInfo(" EncoderReqInterval: {}", to_msecs(hw_cfg.devConfig.EncoderReqInterval)); logInfo(" EncoderReqInterval: {}", to_msecs(hw_cfg.devConfig.EncoderReqInterval));
logInfo(" EncoderSpeedInterval: {}", to_msecs(hw_cfg.devConfig.EncoderSpeedInterval)); logInfo(" EncoderSpeedInterval: {}", to_msecs(hw_cfg.devConfig.EncoderSpeedInterval));
logInfo(" PIDMaxDt: {}", to_msecs(hw_cfg.devConfig.PIDMaxDt));
logInfo(" PIDRefreshDt: {}", to_msecs(hw_cfg.devConfig.PIDRefreshDt));
logInfo(" PIDCycleDt: {}", to_msecs(hw_cfg.devConfig.PIDCycleDt));
logInfo(" XPIDC: [P: {}, I: {}, D: {}]", hw_cfg.devConfig.XPIDC.P, hw_cfg.devConfig.XPIDC.I, logInfo(" XPIDC: [P: {}, I: {}, D: {}]", hw_cfg.devConfig.XPIDC.P, hw_cfg.devConfig.XPIDC.I,
hw_cfg.devConfig.XPIDC.D); hw_cfg.devConfig.XPIDC.D);
@@ -135,6 +138,8 @@ Asibfm700Mount::error_t Asibfm700Mount::initMount()
hw_cfg.devConfig.YPIDC.D); hw_cfg.devConfig.YPIDC.D);
logInfo(" YPIDV: [P: {}, I: {}, D: {}]", hw_cfg.devConfig.YPIDV.P, hw_cfg.devConfig.YPIDV.I, logInfo(" YPIDV: [P: {}, I: {}, D: {}]", hw_cfg.devConfig.YPIDV.P, hw_cfg.devConfig.YPIDV.I,
hw_cfg.devConfig.YPIDV.D); hw_cfg.devConfig.YPIDV.D);
logInfo(" XEncZero: {}", hw_cfg.devConfig.XEncZero);
logInfo(" YEncZero: {}", hw_cfg.devConfig.YEncZero);
// actually, only set this->_hardwareConfig.devConfig part and paths!!! // actually, only set this->_hardwareConfig.devConfig part and paths!!!
this->_hardwareConfig = hw_cfg; this->_hardwareConfig = hw_cfg;
@@ -183,9 +188,47 @@ Asibfm700Mount::error_t Asibfm700Mount::initMount()
mpars.brakingAccelX = _hardwareConfig.hwConfig.Yconf.accel; // Sidereal defines HA-axis as Y-axis mpars.brakingAccelX = _hardwareConfig.hwConfig.Yconf.accel; // Sidereal defines HA-axis as Y-axis
mpars.brakingAccelY = _hardwareConfig.hwConfig.Xconf.accel; // Sidereal defines DEC-axis as X-axis mpars.brakingAccelY = _hardwareConfig.hwConfig.Xconf.accel; // Sidereal defines DEC-axis as X-axis
} else { } else {
mpars.brakingAccelX = MCC_Y_ACCELERATION; // Sidereal defines HA-axis as Y-axis mpars.brakingAccelX = 0.165806; // Sidereal defines HA-axis as Y-axis
mpars.brakingAccelY = MCC_X_ACCELERATION; // Sidereal defines DEC-axis as X-axis mpars.brakingAccelY = 0.219911; // Sidereal defines DEC-axis as X-axis
} }
auto max_dt_intvl = _mountConfig.getValue<std::chrono::milliseconds>("PIDMaxDt").value_or({});
auto min_dt_intvl = _mountConfig.getValue<std::chrono::milliseconds>("PIDRefreshDt").value_or({});
// check for polling interval consistency
auto intvl = mpars.slewingTelemetryInterval;
if (intvl > max_dt_intvl) {
mpars.slewingTelemetryInterval = max_dt_intvl;
logWarn(
" slewingTelemetryInterval user value ({} ms) is greater than allowed! Set it to maximal "
"allowed one: {} ms",
intvl.count(), max_dt_intvl.count());
}
if (intvl < min_dt_intvl) {
mpars.slewingTelemetryInterval = min_dt_intvl;
logWarn(
" slewingTelemetryInterval user value ({} ms) is lesser than allowed! Set it to minimal allowed "
"one: {} ms",
intvl.count(), min_dt_intvl.count());
}
intvl = mpars.trackingTelemetryInterval;
if (intvl > max_dt_intvl) {
mpars.trackingTelemetryInterval = max_dt_intvl;
logWarn(
" trackingTelemetryInterval user value ({} ms) is greater than allowed! Set it to maximal "
"allowed one: {} ms",
intvl.count(), max_dt_intvl.count());
}
if (intvl < min_dt_intvl) {
mpars.trackingTelemetryInterval = min_dt_intvl;
logWarn(
" trackingTelemetryInterval user value ({} ms) is lesser than allowed! Set it to minimal "
"allowed one: {} ms",
intvl.count(), min_dt_intvl.count());
}
auto st_err = setSlewingParams(mpars); auto st_err = setSlewingParams(mpars);
if (st_err) { if (st_err) {
errorLogging(" An error occured while setting slewing parameters: ", st_err); errorLogging(" An error occured while setting slewing parameters: ", st_err);
@@ -194,10 +237,14 @@ Asibfm700Mount::error_t Asibfm700Mount::initMount()
logInfo(" Max DEC-axis speed: {} degs/s", mcc::MccAngle(mpars.slewRateY).degrees()); logInfo(" Max DEC-axis speed: {} degs/s", mcc::MccAngle(mpars.slewRateY).degrees());
logInfo(" HA-axis stop acceleration braking: {} degs/s^2", mcc::MccAngle(mpars.brakingAccelX).degrees()); logInfo(" HA-axis stop acceleration braking: {} degs/s^2", mcc::MccAngle(mpars.brakingAccelX).degrees());
logInfo(" DEC-axis stop acceleration braking: {} degs/s^2", mcc::MccAngle(mpars.brakingAccelY).degrees()); logInfo(" DEC-axis stop acceleration braking: {} degs/s^2", mcc::MccAngle(mpars.brakingAccelY).degrees());
logInfo(" Slewing telemetry polling interval: {} millisecs", mpars.slewingTelemetryInterval.count());
} }
st_err = setTrackingParams(_mountConfig.movingModelParams()); st_err = setTrackingParams(_mountConfig.movingModelParams());
if (st_err) { if (st_err) {
errorLogging(" An error occured while setting tracking parameters: ", st_err); errorLogging(" An error occured while setting tracking parameters: ", st_err);
} else {
logInfo(" Tracking telemetry polling interval: {} millisecs", mpars.trackingTelemetryInterval.count());
} }
logInfo("Slewing and tracking parameters have been set successfully"); logInfo("Slewing and tracking parameters have been set successfully");
@@ -255,15 +302,21 @@ Asibfm700Mount::error_t Asibfm700Mount::initMount()
setStateERFA(std::move(ccte_state)); setStateERFA(std::move(ccte_state));
setTelemetryDataUpdateInterval(_mountConfig.hardwarePollingPeriod()); // setTelemetryDataUpdateInterval(_mountConfig.hardwarePollingPeriod());
setTelemetryUpdateTimeout(_mountConfig.movingModelParams().telemetryTimeout); setTelemetryUpdateTimeout(_mountConfig.movingModelParams().telemetryTimeout);
startInternalTelemetryDataUpdating(); startInternalTelemetryDataUpdating();
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // std::this_thread::sleep_for(std::chrono::milliseconds(100));
bool ok = isInternalTelemetryDataUpdating(); bool ok = isInternalTelemetryDataUpdating();
if (ok) { if (ok) {
logInfo("Start updating telemetry data"); logInfo("Start updating telemetry data ...");
mcc::MccTelemetryData tdata;
auto err = waitForTelemetryData(&tdata, _mountConfig.movingModelParams().telemetryTimeout);
if (err) {
logError("Cannot update telemetry data (err = {} [{}, {}])!", err.message(), err.value(),
err.category().name());
}
} else { } else {
auto err = lastUpdateError(); auto err = lastUpdateError();
logError("Cannot update telemetry data (err = {} [{}, {}])!", err.message(), err.value(), logError("Cannot update telemetry data (err = {} [{}, {}])!", err.message(), err.value(),

View File

@@ -22,14 +22,14 @@ class Asibfm700Mount : public Asibfm700CCTE,
mcc::MccTelemetry, mcc::MccTelemetry,
Asibfm700PZoneContainer, Asibfm700PZoneContainer,
Asibfm700SlewingModel, Asibfm700SlewingModel,
mcc::MccSimpleTrackingModel, Asibfm700TrackingModel,
Asibfm700Logger> Asibfm700Logger>
{ {
typedef mcc::MccGenericMount<AsibFM700ServoController, typedef mcc::MccGenericMount<AsibFM700ServoController,
mcc::MccTelemetry, mcc::MccTelemetry,
Asibfm700PZoneContainer, Asibfm700PZoneContainer,
Asibfm700SlewingModel, Asibfm700SlewingModel,
mcc::MccSimpleTrackingModel, Asibfm700TrackingModel,
Asibfm700Logger> Asibfm700Logger>
gm_class_t; gm_class_t;

View File

@@ -111,16 +111,17 @@ int main(int argc, char* argv[])
logger->info("\n"); logger->info("\n");
logger->set_pattern("[%Y-%m-%d %T.%e][%l]: %v");
std::string mount_cfg_fname = opt_result["config"].as<std::string>(); std::string mount_cfg_fname = opt_result["config"].as<std::string>();
if (mount_cfg_fname.size()) { if (mount_cfg_fname.size()) {
logger->info("Try to load mount configuration from file: {}", mount_cfg_fname); logger->info("Try to load mount configuration from file: {}", mount_cfg_fname);
auto err = mount_cfg.load(mount_cfg_fname); auto err = mount_cfg.load(mount_cfg_fname);
if (err) { if (err) {
logger->error("Cannot load mount configuration (err = {})! Use defaults!", err.message()); logger->error("Cannot load mount configuration (err = {})! Use of defaults!", err.message());
} else { } else {
logger->info("Mount configuration was loaded successfully!"); logger->info("Mount configuration was loaded successfully!");
} }
logger->info("\n");
} }
asibfm700::Asibfm700Mount mount(mount_cfg, logger); asibfm700::Asibfm700Mount mount(mount_cfg, logger);

View File

@@ -106,22 +106,71 @@ AsibFM700ServoController::error_t AsibFM700ServoController::hardwareInit()
AsibFM700ServoController::error_t AsibFM700ServoController::hardwareSetState(hardware_state_t state) AsibFM700ServoController::error_t AsibFM700ServoController::hardwareSetState(hardware_state_t state)
{ {
std::lock_guard lock{*_setStateMutex};
if (state.moving_state == hardware_moving_state_t::HW_MOVE_STOPPED) { // stop!
error_t err = static_cast<AsibFM700ServoControllerErrorCode>(Mount.stop());
if (err) {
return err;
}
hardware_state_t hw_state;
auto start_tp = std::chrono::steady_clock::now();
// poll hardware till stopped-state detected ...
while (true) {
err = hardwareGetState(&hw_state);
if (err) {
return err;
}
if (hw_state.moving_state == hardware_moving_state_t::HW_MOVE_STOPPED) {
break;
}
if ((std::chrono::steady_clock::now() - start_tp) > _hardwareConfig.pollingTimeout) {
err = AsibFM700ServoControllerErrorCode::ERROR_POLLING_TIMEOUT;
break;
}
std::this_thread::sleep_for(_hardwareConfig.pollingInterval);
}
return err;
}
// static thread_local coordval_pair_t cvalpair{.X{0.0, 0.0}, .Y{0.0, 0.0}};
// static thread_local coordpair_t cpair{.X = 0.0, .Y = 0.0};
// cvalpair.X = {.val = state.Y, .t = tp};
// cvalpair.Y = {.val = state.X, .t = tp};
// cpair.X = state.tagY;
// cpair.Y = state.tagX;
// time point from sidservo library is 'double' number represented UNIXTIME with // time point from sidservo library is 'double' number represented UNIXTIME with
// microseconds/nanoseconds precision // microseconds/nanoseconds precision
double tp = std::chrono::duration<double>(state.time_point.time_since_epoch()).count(); // double tp = std::chrono::duration<double>(state.time_point.time_since_epoch()).count();
std::lock_guard lock{*_setStateMutex};
// 2025-12-04: coordval_pair_t.X.t is now of type struct timespec
auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(state.time_point.time_since_epoch());
auto secs = std::chrono::floor<std::chrono::seconds>(ns);
ns -= secs;
std::timespec tp{.tv_sec = secs.count(), .tv_nsec = ns.count()};
// according to"SiTech protocol notes" X is DEC-axis and Y is HA-axis // according to"SiTech protocol notes" X is DEC-axis and Y is HA-axis
coordval_pair_t cvalpair{.X{.val = state.Y, .t = tp}, .Y{.val = state.X, .t = tp}}; coordval_pair_t cvalpair{.X{.val = state.Y, .t = tp}, .Y{.val = state.X, .t = tp}};
coordpair_t cpair{.X = state.Y, .Y = state.X}; // coordpair_t cpair{.X = state.endptY, .Y = state.endptX};
// coordpair_t cpair{.X = state.Y, .Y = state.X + mcc::MccAngle(10.0_arcsecs)};
// correctTo is asynchronous function!!! // correctTo is asynchronous function!!!
// //
// according to the Eddy's implementation of the LibSidServo library it is safe // according to the Eddy's implementation of the LibSidServo library it is safe
// to pass the addresses of 'cvalpair' and 'cpair' automatic variables // to pass the addresses of 'cvalpair' and 'cpair' automatic variables
auto err = static_cast<AsibFM700ServoControllerErrorCode>(Mount.correctTo(&cvalpair, &cpair)); // auto err = static_cast<AsibFM700ServoControllerErrorCode>(Mount.correctTo(&cvalpair, &cpair));
auto err = static_cast<AsibFM700ServoControllerErrorCode>(Mount.correctTo(&cvalpair));
return err; return err;
} }
@@ -141,14 +190,22 @@ AsibFM700ServoController::error_t AsibFM700ServoController::hardwareGetState(har
// time point from sidservo library is 'double' number represented UNIXTIME with // time point from sidservo library is 'double' number represented UNIXTIME with
// microseconds/nanoseconds precision (must be equal for encXposition and encYposition) // microseconds/nanoseconds precision (must be equal for encXposition and encYposition)
using secs_t = std::chrono::duration<double>; // using secs_t = std::chrono::duration<double>;
secs_t secs = secs_t{mdata.encXposition.t}; // secs_t secs = secs_t{mdata.encXposition.t};
if (mcc::utils::isEqual(secs.count(), 0.0)) { // model mode? // state->time_point = tp_t{std::chrono::duration_cast<tp_t::duration>(secs)};
state->time_point = decltype(state->time_point)::clock::now();
} else { // 2025-12-04: coordval_pair_t.X.t is now of type struct timespec
state->time_point = tp_t{std::chrono::duration_cast<tp_t::duration>(secs)}; auto dr = std::chrono::duration_cast<decltype(state->time_point)::duration>(
} std::chrono::seconds(mdata.encXposition.t.tv_sec) + std::chrono::nanoseconds(mdata.encXposition.t.tv_nsec));
state->time_point = decltype(state->time_point){dr};
// if (mcc::utils::isEqual(secs.count(), 0.0)) { // model mode?
// state->time_point = decltype(state->time_point)::clock::now();
// } else {
// state->time_point = tp_t{std::chrono::duration_cast<tp_t::duration>(secs)};
// }
// WARNING: TEMPORARY (WAIT FOR Eddy fix its implementation of LibSidServo)!!! // WARNING: TEMPORARY (WAIT FOR Eddy fix its implementation of LibSidServo)!!!
// state->time_point = decltype(state->time_point)::clock::now(); // state->time_point = decltype(state->time_point)::clock::now();
@@ -159,33 +216,43 @@ AsibFM700ServoController::error_t AsibFM700ServoController::hardwareGetState(har
state->speedX = mdata.encYspeed.val; state->speedX = mdata.encYspeed.val;
state->speedY = mdata.encXspeed.val; state->speedY = mdata.encXspeed.val;
state->stateX = mdata.Xstate; state->stateX = mdata.Ystate;
state->stateY = mdata.Ystate; state->stateY = mdata.Xstate;
if (mdata.Xstate == AXIS_STOPPED) { if (mdata.Xstate == AXIS_ERROR || mdata.Ystate == AXIS_ERROR) {
if (mdata.Ystate == AXIS_STOPPED) { state->moving_state = hardware_moving_state_t::HW_MOVE_ERROR;
state->moving_state = hardware_moving_state_t::HW_MOVE_STOPPED; } else {
} else if (mdata.Ystate == AXIS_SLEWING) { if (mdata.Xstate == AXIS_STOPPED) {
if (mdata.Ystate == AXIS_STOPPED) {
state->moving_state = hardware_moving_state_t::HW_MOVE_STOPPED;
} else if (mdata.Ystate == AXIS_SLEWING) {
state->moving_state = hardware_moving_state_t::HW_MOVE_SLEWING;
} else if (mdata.Ystate == AXIS_POINTING) {
state->moving_state = hardware_moving_state_t::HW_MOVE_ADJUSTING;
} else if (mdata.Ystate == AXIS_GUIDING) {
state->moving_state = hardware_moving_state_t::HW_MOVE_GUIDING;
} else {
state->moving_state = hardware_moving_state_t::HW_MOVE_UNKNOWN;
}
} else if (mdata.Xstate == AXIS_SLEWING) {
state->moving_state = hardware_moving_state_t::HW_MOVE_SLEWING; state->moving_state = hardware_moving_state_t::HW_MOVE_SLEWING;
} else if (mdata.Ystate == AXIS_POINTING) { } else if (mdata.Xstate == AXIS_POINTING) {
state->moving_state = hardware_moving_state_t::HW_MOVE_ADJUSTING; if (mdata.Ystate == AXIS_SLEWING) {
} else if (mdata.Ystate == AXIS_GUIDING) { state->moving_state = hardware_moving_state_t::HW_MOVE_SLEWING;
state->moving_state = hardware_moving_state_t::HW_MOVE_GUIDING; } else {
} else if (mdata.Ystate == AXIS_ERROR) { state->moving_state = hardware_moving_state_t::HW_MOVE_ADJUSTING;
state->moving_state = hardware_moving_state_t::HW_MOVE_ERROR; }
} else if (mdata.Xstate == AXIS_GUIDING) {
if (mdata.Ystate == AXIS_SLEWING) {
state->moving_state = hardware_moving_state_t::HW_MOVE_SLEWING;
} else if (mdata.Ystate == AXIS_POINTING) {
state->moving_state = hardware_moving_state_t::HW_MOVE_ADJUSTING;
} else {
state->moving_state = hardware_moving_state_t::HW_MOVE_GUIDING;
}
} else { } else {
state->moving_state = hardware_moving_state_t::HW_MOVE_UNKNOWN; state->moving_state = hardware_moving_state_t::HW_MOVE_UNKNOWN;
} }
} else if (mdata.Xstate == AXIS_SLEWING) {
state->moving_state = hardware_moving_state_t::HW_MOVE_SLEWING;
} else if (mdata.Xstate == AXIS_POINTING) {
state->moving_state = hardware_moving_state_t::HW_MOVE_ADJUSTING;
} else if (mdata.Xstate == AXIS_GUIDING) {
state->moving_state = hardware_moving_state_t::HW_MOVE_GUIDING;
} else if (mdata.Xstate == AXIS_ERROR) {
state->moving_state = hardware_moving_state_t::HW_MOVE_ERROR;
} else {
state->moving_state = hardware_moving_state_t::HW_MOVE_UNKNOWN;
} }
} }

View File

@@ -78,6 +78,12 @@ public:
axis_status_t stateX, stateY; // Eddy's LibSidServo axis state axis_status_t stateX, stateY; // Eddy's LibSidServo axis state
hardware_moving_state_t moving_state; hardware_moving_state_t moving_state;
// endpoint: a point on the trajectory of movement behind the guidance point (X,Y), taking into account
// the movement vector (i.e. sign of movement speed)
// this point is needed as Sidereal controller commands require not only moving speed but
// also 'target' point (point at which mount will stop)
// double endptX, endptY;
}; };

View File

@@ -6,13 +6,22 @@ set(ASIO_FOUND FALSE)
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
find_path(ASIO_DIR asio.hpp HINTS ${ASIO_INSTALL_DIR} PATH_SUFFIXES include) set(ASIO_INSTALL_DIR "" CACHE STRING "ASIO install dir")
set(ASIO_INSTALL_DIR_INTERNAL "" CACHE STRING "ASIO install dir")
if(NOT "${ASIO_INSTALL_DIR}" STREQUAL "${ASIO_INSTALL_DIR_INTERNAL}") # ASIO_INSTALL_DIR is given in command-line
unset(ASIO_INCLUDE_DIR CACHE)
find_path(ASIO_DIR asio.hpp HINTS ${ASIO_INSTALL_DIR} PATH_SUFFIXES include)
else() # in system path
find_path(ASIO_DIR asio.hpp PATH_SUFFIXES include)
endif()
if (NOT ASIO_DIR) if (NOT ASIO_DIR)
message(WARNING "Cannot find ASIO library headers!") message(WARNING "Cannot find ASIO library headers!")
set(ASIO_FOUND FALSE) set(ASIO_FOUND FALSE)
else() else()
message(STATUS "Found ASIO: TRUE (${ASIO_DIR})") message(STATUS "Found ASIO: (${ASIO_DIR})")
# ASIO is header-only library so it is IMPORTED target # ASIO is header-only library so it is IMPORTED target
add_library(ASIO::ASIO INTERFACE IMPORTED GLOBAL) add_library(ASIO::ASIO INTERFACE IMPORTED GLOBAL)

View File

@@ -7,6 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
find_package(Threads REQUIRED)
# ******* SPDLOG LIBRARY ******* # ******* SPDLOG LIBRARY *******
@@ -61,7 +62,8 @@ set_target_properties(ERFA_LIB PROPERTIES IMPORTED_LOCATION ${CMAKE_BINARY_DIR}/
add_dependencies(ERFA_LIB erfalib) add_dependencies(ERFA_LIB erfalib)
set(ERFA_INCLUDE_DIR ${CMAKE_BINARY_DIR}/erfa_lib) set(ERFA_INCLUDE_DIR ${CMAKE_BINARY_DIR}/erfa_lib)
# set(ERFA_LIBFILE ${CMAKE_BINARY_DIR}/erfa_lib/liberfa.a PARENT_SCOPE) # set(ERFA_LIBFILE ${CMAKE_BINARY_DIR}/erfa_lib/liberfa.a PARENT_SCOPE)
# include_directories(${ERFA_INCLUDE_DIR}) include_directories(${ERFA_INCLUDE_DIR})
message(STATUS "ERFA INCLUDE DIR: " ${ERFA_INCLUDE_DIR})
add_subdirectory(bsplines) add_subdirectory(bsplines)
message(STATUS "BSPLINES_INCLUDE_DIR: " ${BSPLINES_INCLUDE_DIR}) message(STATUS "BSPLINES_INCLUDE_DIR: " ${BSPLINES_INCLUDE_DIR})
@@ -79,7 +81,7 @@ set(MCC_LIBRARY mcc)
add_library(${MCC_LIBRARY} INTERFACE ${MCC_LIBRARY_SRC}) add_library(${MCC_LIBRARY} INTERFACE ${MCC_LIBRARY_SRC})
target_compile_features(${MCC_LIBRARY} INTERFACE cxx_std_23) target_compile_features(${MCC_LIBRARY} INTERFACE cxx_std_23)
target_compile_definitions(${MCC_LIBRARY} INTERFACE SPDLOG_USE_STD_FORMAT=1 SPDLOG_FMT_EXTERNAL=0) target_compile_definitions(${MCC_LIBRARY} INTERFACE SPDLOG_USE_STD_FORMAT=1 SPDLOG_FMT_EXTERNAL=0)
target_link_libraries(${MCC_LIBRARY} INTERFACE spdlog) target_link_libraries(${MCC_LIBRARY} INTERFACE spdlog Threads::Threads atomic ${ERFA_LIB})
target_include_directories(${MCC_LIBRARY} INTERFACE ${ERFA_INCLUDE_DIR} ${BSPLINES_INCLUDE_DIR}) target_include_directories(${MCC_LIBRARY} INTERFACE ${ERFA_INCLUDE_DIR} ${BSPLINES_INCLUDE_DIR})
target_include_directories(${MCC_LIBRARY} INTERFACE target_include_directories(${MCC_LIBRARY} INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
@@ -99,5 +101,10 @@ if (WITH_TESTS)
add_executable(${NETMSG_TESTS_APP} tests/netmsg_test.cpp) add_executable(${NETMSG_TESTS_APP} tests/netmsg_test.cpp)
target_link_libraries(${NETMSG_TESTS_APP} mcc) target_link_libraries(${NETMSG_TESTS_APP} mcc)
set(MCCCOORD_TEST_APP mcc_coord_test)
add_executable(${MCCCOORD_TEST_APP} tests/mcc_coord_test.cpp)
target_link_libraries(${MCCCOORD_TEST_APP} mcc ERFA_LIB)
enable_testing() enable_testing()
endif() endif()

View File

@@ -459,11 +459,31 @@ class MccAngleDEC_APP : public MccAngle
using MccAngle::MccAngle; using MccAngle::MccAngle;
}; };
class MccAngleRA_OBS : public MccAngle
{
using MccAngle::MccAngle;
};
class MccAngleDEC_OBS : public MccAngle
{
using MccAngle::MccAngle;
};
class MccAngleHA : public MccAngle class MccAngleHA : public MccAngle
{ {
using MccAngle::MccAngle; using MccAngle::MccAngle;
}; };
class MccAngleHA_APP : public MccAngle
{
using MccAngle::MccAngle;
};
class MccAngleHA_OBS : public MccAngle
{
using MccAngle::MccAngle;
};
class MccAngleAZ : public MccAngle class MccAngleAZ : public MccAngle
{ {
using MccAngle::MccAngle; using MccAngle::MccAngle;
@@ -516,7 +536,11 @@ enum class MccCoordKind : size_t {
COORDS_KIND_DEC_ICRS = traits::mcc_type_hash<MccAngleDEC_ICRS>, COORDS_KIND_DEC_ICRS = traits::mcc_type_hash<MccAngleDEC_ICRS>,
COORDS_KIND_RA_APP = traits::mcc_type_hash<MccAngleRA_APP>, COORDS_KIND_RA_APP = traits::mcc_type_hash<MccAngleRA_APP>,
COORDS_KIND_DEC_APP = traits::mcc_type_hash<MccAngleDEC_APP>, COORDS_KIND_DEC_APP = traits::mcc_type_hash<MccAngleDEC_APP>,
COORDS_KIND_RA_OBS = traits::mcc_type_hash<MccAngleRA_OBS>,
COORDS_KIND_DEC_OBS = traits::mcc_type_hash<MccAngleDEC_OBS>,
COORDS_KIND_HA = traits::mcc_type_hash<MccAngleHA>, COORDS_KIND_HA = traits::mcc_type_hash<MccAngleHA>,
COORDS_KIND_HA_APP = traits::mcc_type_hash<MccAngleHA_APP>,
COORDS_KIND_HA_OBS = traits::mcc_type_hash<MccAngleHA_OBS>,
COORDS_KIND_AZ = traits::mcc_type_hash<MccAngleAZ>, COORDS_KIND_AZ = traits::mcc_type_hash<MccAngleAZ>,
COORDS_KIND_ZD = traits::mcc_type_hash<MccAngleZD>, COORDS_KIND_ZD = traits::mcc_type_hash<MccAngleZD>,
COORDS_KIND_ALT = traits::mcc_type_hash<MccAngleALT>, COORDS_KIND_ALT = traits::mcc_type_hash<MccAngleALT>,
@@ -531,18 +555,43 @@ enum class MccCoordPairKind : size_t {
COORDS_KIND_GENERIC = traits::mcc_type_pair_hash<MccAngle, MccAngle>(), COORDS_KIND_GENERIC = traits::mcc_type_pair_hash<MccAngle, MccAngle>(),
COORDS_KIND_RADEC_ICRS = traits::mcc_type_pair_hash<MccAngleRA_ICRS, MccAngleDEC_ICRS>(), COORDS_KIND_RADEC_ICRS = traits::mcc_type_pair_hash<MccAngleRA_ICRS, MccAngleDEC_ICRS>(),
COORDS_KIND_RADEC_APP = traits::mcc_type_pair_hash<MccAngleRA_APP, MccAngleDEC_APP>(), COORDS_KIND_RADEC_APP = traits::mcc_type_pair_hash<MccAngleRA_APP, MccAngleDEC_APP>(),
COORDS_KIND_RADEC_OBS = traits::mcc_type_pair_hash<MccAngleRA_OBS, MccAngleDEC_OBS>(),
COORDS_KIND_HADEC_APP = traits::mcc_type_pair_hash<MccAngleHA, MccAngleDEC_APP>(), COORDS_KIND_HADEC_APP = traits::mcc_type_pair_hash<MccAngleHA, MccAngleDEC_APP>(),
// COORDS_KIND_HADEC_APP = traits::mcc_type_pair_hash<MccAngleHA_APP, MccAngleDEC_APP>(),
COORDS_KIND_HADEC_OBS = traits::mcc_type_pair_hash<MccAngleHA_OBS, MccAngleDEC_OBS>(),
COORDS_KIND_AZZD = traits::mcc_type_pair_hash<MccAngleAZ, MccAngleZD>(), COORDS_KIND_AZZD = traits::mcc_type_pair_hash<MccAngleAZ, MccAngleZD>(),
COORDS_KIND_AZALT = traits::mcc_type_pair_hash<MccAngleAZ, MccAngleALT>(), COORDS_KIND_AZALT = traits::mcc_type_pair_hash<MccAngleAZ, MccAngleALT>(),
COORDS_KIND_XY = traits::mcc_type_pair_hash<MccAngleX, MccAngleY>(), COORDS_KIND_XY = traits::mcc_type_pair_hash<MccAngleX, MccAngleY>(),
COORDS_KIND_LATLON = traits::mcc_type_pair_hash<MccAngleLAT, MccAngleLON>(), COORDS_KIND_LONLAT = traits::mcc_type_pair_hash<MccAngleLON, MccAngleLAT>(),
COORDS_KIND_UNKNOWN = traits::mcc_type_pair_hash<MccAngleUnknown, MccAngleUnknown>() COORDS_KIND_UNKNOWN = traits::mcc_type_pair_hash<MccAngleUnknown, MccAngleUnknown>()
}; };
template <MccCoordPairKind PK>
static constexpr bool mccIsObsCoordPairKind =
(PK == MccCoordPairKind::COORDS_KIND_RADEC_OBS || PK == MccCoordPairKind::COORDS_KIND_HADEC_OBS ||
PK == MccCoordPairKind::COORDS_KIND_AZZD || PK == MccCoordPairKind::COORDS_KIND_AZALT);
static constexpr bool mcc_is_obs_coordpair(MccCoordPairKind kind)
{
return kind == MccCoordPairKind::COORDS_KIND_RADEC_OBS || kind == MccCoordPairKind::COORDS_KIND_HADEC_OBS ||
kind == MccCoordPairKind::COORDS_KIND_AZZD || kind == MccCoordPairKind::COORDS_KIND_AZALT;
};
template <MccCoordPairKind PK>
static constexpr bool mccIsAppCoordPairKind =
(PK == MccCoordPairKind::COORDS_KIND_RADEC_APP || PK == MccCoordPairKind::COORDS_KIND_HADEC_APP);
static constexpr bool mcc_is_app_coordpair(MccCoordPairKind kind)
{
return kind == MccCoordPairKind::COORDS_KIND_RADEC_APP || kind == MccCoordPairKind::COORDS_KIND_HADEC_APP;
};
static constexpr std::string_view MCC_COORDPAIR_KIND_RADEC_ICRS_STR = "RADEC-IRCS"; static constexpr std::string_view MCC_COORDPAIR_KIND_RADEC_ICRS_STR = "RADEC-IRCS";
static constexpr std::string_view MCC_COORDPAIR_KIND_RADEC_APP_STR = "RADEC-APP"; static constexpr std::string_view MCC_COORDPAIR_KIND_RADEC_APP_STR = "RADEC-APP";
static constexpr std::string_view MCC_COORDPAIR_KIND_RADEC_OBS_STR = "RADEC-OBS";
static constexpr std::string_view MCC_COORDPAIR_KIND_HADEC_APP_STR = "HADEC-APP"; static constexpr std::string_view MCC_COORDPAIR_KIND_HADEC_APP_STR = "HADEC-APP";
static constexpr std::string_view MCC_COORDPAIR_KIND_HADEC_OBS_STR = "HADEC-OBS";
static constexpr std::string_view MCC_COORDPAIR_KIND_AZALT_STR = "AZALT"; static constexpr std::string_view MCC_COORDPAIR_KIND_AZALT_STR = "AZALT";
static constexpr std::string_view MCC_COORDPAIR_KIND_AZZD_STR = "AZZD"; static constexpr std::string_view MCC_COORDPAIR_KIND_AZZD_STR = "AZZD";
static constexpr std::string_view MCC_COORDPAIR_KIND_XY_STR = "XY"; static constexpr std::string_view MCC_COORDPAIR_KIND_XY_STR = "XY";
@@ -554,11 +603,13 @@ template <MccCoordPairKind KIND>
static constexpr std::string_view MccCoordPairKindStr = static constexpr std::string_view MccCoordPairKindStr =
KIND == MccCoordPairKind::COORDS_KIND_RADEC_ICRS ? MCC_COORDPAIR_KIND_RADEC_ICRS_STR KIND == MccCoordPairKind::COORDS_KIND_RADEC_ICRS ? MCC_COORDPAIR_KIND_RADEC_ICRS_STR
: KIND == MccCoordPairKind::COORDS_KIND_RADEC_APP ? MCC_COORDPAIR_KIND_RADEC_APP_STR : KIND == MccCoordPairKind::COORDS_KIND_RADEC_APP ? MCC_COORDPAIR_KIND_RADEC_APP_STR
: KIND == MccCoordPairKind::COORDS_KIND_RADEC_OBS ? MCC_COORDPAIR_KIND_RADEC_OBS_STR
: KIND == MccCoordPairKind::COORDS_KIND_HADEC_APP ? MCC_COORDPAIR_KIND_HADEC_APP_STR : KIND == MccCoordPairKind::COORDS_KIND_HADEC_APP ? MCC_COORDPAIR_KIND_HADEC_APP_STR
: KIND == MccCoordPairKind::COORDS_KIND_HADEC_OBS ? MCC_COORDPAIR_KIND_HADEC_OBS_STR
: KIND == MccCoordPairKind::COORDS_KIND_AZALT ? MCC_COORDPAIR_KIND_AZALT_STR : KIND == MccCoordPairKind::COORDS_KIND_AZALT ? MCC_COORDPAIR_KIND_AZALT_STR
: KIND == MccCoordPairKind::COORDS_KIND_AZZD ? MCC_COORDPAIR_KIND_AZZD_STR : KIND == MccCoordPairKind::COORDS_KIND_AZZD ? MCC_COORDPAIR_KIND_AZZD_STR
: KIND == MccCoordPairKind::COORDS_KIND_XY ? MCC_COORDPAIR_KIND_XY_STR : KIND == MccCoordPairKind::COORDS_KIND_XY ? MCC_COORDPAIR_KIND_XY_STR
: KIND == MccCoordPairKind::COORDS_KIND_LATLON ? MCC_COORDPAIR_KIND_LATLON_STR : KIND == MccCoordPairKind::COORDS_KIND_LONLAT ? MCC_COORDPAIR_KIND_LATLON_STR
: KIND == MccCoordPairKind::COORDS_KIND_GENERIC ? MCC_COORDPAIR_KIND_GENERIC_STR : KIND == MccCoordPairKind::COORDS_KIND_GENERIC ? MCC_COORDPAIR_KIND_GENERIC_STR
: MCC_COORDPAIR_KIND_UNKNOWN_STR; : MCC_COORDPAIR_KIND_UNKNOWN_STR;
@@ -567,11 +618,13 @@ static constexpr std::string_view MccCoordPairKindToStr(MccCoordPairKind KIND)
{ {
return KIND == MccCoordPairKind::COORDS_KIND_RADEC_ICRS ? MCC_COORDPAIR_KIND_RADEC_ICRS_STR return KIND == MccCoordPairKind::COORDS_KIND_RADEC_ICRS ? MCC_COORDPAIR_KIND_RADEC_ICRS_STR
: KIND == MccCoordPairKind::COORDS_KIND_RADEC_APP ? MCC_COORDPAIR_KIND_RADEC_APP_STR : KIND == MccCoordPairKind::COORDS_KIND_RADEC_APP ? MCC_COORDPAIR_KIND_RADEC_APP_STR
: KIND == MccCoordPairKind::COORDS_KIND_RADEC_OBS ? MCC_COORDPAIR_KIND_RADEC_OBS_STR
: KIND == MccCoordPairKind::COORDS_KIND_HADEC_APP ? MCC_COORDPAIR_KIND_HADEC_APP_STR : KIND == MccCoordPairKind::COORDS_KIND_HADEC_APP ? MCC_COORDPAIR_KIND_HADEC_APP_STR
: KIND == MccCoordPairKind::COORDS_KIND_HADEC_OBS ? MCC_COORDPAIR_KIND_HADEC_OBS_STR
: KIND == MccCoordPairKind::COORDS_KIND_AZALT ? MCC_COORDPAIR_KIND_AZALT_STR : KIND == MccCoordPairKind::COORDS_KIND_AZALT ? MCC_COORDPAIR_KIND_AZALT_STR
: KIND == MccCoordPairKind::COORDS_KIND_AZZD ? MCC_COORDPAIR_KIND_AZZD_STR : KIND == MccCoordPairKind::COORDS_KIND_AZZD ? MCC_COORDPAIR_KIND_AZZD_STR
: KIND == MccCoordPairKind::COORDS_KIND_XY ? MCC_COORDPAIR_KIND_XY_STR : KIND == MccCoordPairKind::COORDS_KIND_XY ? MCC_COORDPAIR_KIND_XY_STR
: KIND == MccCoordPairKind::COORDS_KIND_LATLON ? MCC_COORDPAIR_KIND_LATLON_STR : KIND == MccCoordPairKind::COORDS_KIND_LONLAT ? MCC_COORDPAIR_KIND_LATLON_STR
: KIND == MccCoordPairKind::COORDS_KIND_GENERIC ? MCC_COORDPAIR_KIND_GENERIC_STR : KIND == MccCoordPairKind::COORDS_KIND_GENERIC ? MCC_COORDPAIR_KIND_GENERIC_STR
: MCC_COORDPAIR_KIND_UNKNOWN_STR; : MCC_COORDPAIR_KIND_UNKNOWN_STR;
} }
@@ -588,11 +641,13 @@ static constexpr MccCoordPairKind MccCoordStrToPairKind(R&& spair)
return hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_RADEC_ICRS_STR) ? MccCoordPairKind::COORDS_KIND_RADEC_ICRS return hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_RADEC_ICRS_STR) ? MccCoordPairKind::COORDS_KIND_RADEC_ICRS
: hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_RADEC_APP_STR) ? MccCoordPairKind::COORDS_KIND_RADEC_APP : hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_RADEC_APP_STR) ? MccCoordPairKind::COORDS_KIND_RADEC_APP
: hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_RADEC_OBS_STR) ? MccCoordPairKind::COORDS_KIND_RADEC_OBS
: hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_HADEC_APP_STR) ? MccCoordPairKind::COORDS_KIND_HADEC_APP : hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_HADEC_APP_STR) ? MccCoordPairKind::COORDS_KIND_HADEC_APP
: hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_HADEC_OBS_STR) ? MccCoordPairKind::COORDS_KIND_HADEC_OBS
: hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_AZALT_STR) ? MccCoordPairKind::COORDS_KIND_AZALT : hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_AZALT_STR) ? MccCoordPairKind::COORDS_KIND_AZALT
: hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_AZZD_STR) ? MccCoordPairKind::COORDS_KIND_AZZD : hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_AZZD_STR) ? MccCoordPairKind::COORDS_KIND_AZZD
: hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_XY_STR) ? MccCoordPairKind::COORDS_KIND_XY : hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_XY_STR) ? MccCoordPairKind::COORDS_KIND_XY
: hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_LATLON_STR) ? MccCoordPairKind::COORDS_KIND_LATLON : hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_LATLON_STR) ? MccCoordPairKind::COORDS_KIND_LONLAT
: hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_GENERIC_STR) ? MccCoordPairKind::COORDS_KIND_GENERIC : hash == mcc::utils::FNV1aHash(MCC_COORDPAIR_KIND_GENERIC_STR) ? MccCoordPairKind::COORDS_KIND_GENERIC
: MccCoordPairKind::COORDS_KIND_UNKNOWN; : MccCoordPairKind::COORDS_KIND_UNKNOWN;
} }

View File

@@ -505,8 +505,8 @@ public:
// first, compute HA from CIO-based RA!!! // first, compute HA from CIO-based RA!!!
lst_eo(); lst_eo();
if (!ret) { if (!ret) {
// ha = MccAngle(lst - from_pt.X + eo).normalize<MccAngle::NORM_KIND_0_360>(); ha = MccAngle(lst - from_pt.X + eo).normalize<MccAngle::NORM_KIND_0_360>();
ha = MccAngle(lst - from_pt.X - eo).normalize<MccAngle::NORM_KIND_0_360>(); // ha = MccAngle(lst - from_pt.X - eo).normalize<MccAngle::NORM_KIND_180_180>();
} else { } else {
return ret; return ret;
} }
@@ -533,8 +533,8 @@ public:
} else if (to_pt->pair_kind == MccCoordPairKind::COORDS_KIND_RADEC_APP) { } else if (to_pt->pair_kind == MccCoordPairKind::COORDS_KIND_RADEC_APP) {
lst_eo(); lst_eo();
if (!ret) { if (!ret) {
// to_pt->X = MccAngle(lst - from_pt.X + eo).normalize<MccAngle::NORM_KIND_0_360>(); to_pt->X = MccAngle(lst - from_pt.X + eo).normalize<MccAngle::NORM_KIND_0_360>();
to_pt->X = MccAngle(lst - from_pt.X - eo).normalize<MccAngle::NORM_KIND_0_360>(); // to_pt->X = MccAngle(lst - from_pt.X - eo).normalize<MccAngle::NORM_KIND_0_360>();
to_pt->Y = from_pt.Y; to_pt->Y = from_pt.Y;
} else { } else {
return ret; return ret;
@@ -558,8 +558,8 @@ public:
azalt2hadec(from_pt, to_pt); azalt2hadec(from_pt, to_pt);
lst_eo(); lst_eo();
if (!ret) { if (!ret) {
// to_pt->X = MccAngle(lst - to_pt->X + eo).normalize<MccAngle::NORM_KIND_0_360>(); to_pt->X = MccAngle(lst - to_pt->X + eo).normalize<MccAngle::NORM_KIND_0_360>();
to_pt->X = MccAngle(lst - to_pt->X - eo).normalize<MccAngle::NORM_KIND_0_360>(); // to_pt->X = MccAngle(lst - to_pt->X - eo).normalize<MccAngle::NORM_KIND_0_360>();
} }
} else { } else {
ret = MccCCTE_ERFAErrorCode::ERROR_UNSUPPORTED_COORD_PAIR; ret = MccCCTE_ERFAErrorCode::ERROR_UNSUPPORTED_COORD_PAIR;
@@ -590,7 +590,24 @@ public:
// the main scenario: from ICRS to apparent // the main scenario: from ICRS to apparent
if (from_pt.pair_kind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS) { if (from_pt.pair_kind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS) {
to_pt->RA_ICRS = from_pt.X;
to_pt->DEC_ICRS = from_pt.Y;
return icrs2obs(from_pt, to_pt); return icrs2obs(from_pt, to_pt);
} else { // from apparent (2025-12-18: according to addition of RA/DEC_OCRS to mcc_eqt_hrz_coord_c)
cpt.pair_kind = MccCoordPairKind::COORDS_KIND_RADEC_ICRS;
ret = transformCoordinates(from_pt, &cpt); // to ICRS
if (ret) {
return ret;
}
to_pt->RA_ICRS = cpt.X;
to_pt->DEC_ICRS = cpt.Y;
// if (!ret) {
// ret = transformCoordinates(cpt, to_pt); // from ICRS to observed
// }
// return ret;
} }
// from apparent: copy corresponded coordinates and compute other ones (ignore to_pt->pair_kind) // from apparent: copy corresponded coordinates and compute other ones (ignore to_pt->pair_kind)

838
mcc/mcc_ccte_erfa_new.h Normal file
View File

@@ -0,0 +1,838 @@
#pragma once
#include <erfa.h>
#include <erfam.h>
#include <mutex>
#include "mcc_ccte_iers.h"
// #include "mcc_coord.h"
#include "mcc_generics.h"
namespace mcc::ccte::erfa
{
enum class MccCCTE_ERFAErrorCode : int {
ERROR_OK = 0,
ERROR_NULLPTR,
ERROR_INVALID_INPUT_ARG,
ERROR_julday_INVALID_YEAR,
ERROR_julday_INVALID_MONTH,
ERROR_julday_INVALID_DAY,
ERROR_UNSUPPORTED_COORD_PAIR,
ERROR_BULLETINA_OUT_OF_RANGE,
ERROR_LEAPSECONDS_OUT_OF_RANGE,
ERROR_DUBIOUS_YEAR,
ERROR_UNACCEPTABLE_DATE,
ERROR_UPDATE_LEAPSECONDS,
ERROR_UPDATE_BULLETINA,
ERROR_UNEXPECTED
};
} // namespace mcc::ccte::erfa
namespace std
{
template <>
class is_error_code_enum<mcc::ccte::erfa::MccCCTE_ERFAErrorCode> : public true_type
{
};
} // namespace std
namespace mcc::ccte::erfa
{
/* error category definition */
// error category
struct MccCCTE_ERFACategory : public std::error_category {
MccCCTE_ERFACategory() : std::error_category() {}
const char* name() const noexcept
{
return "CCTE-ERFA";
}
std::string message(int ec) const
{
MccCCTE_ERFAErrorCode err = static_cast<MccCCTE_ERFAErrorCode>(ec);
switch (err) {
case MccCCTE_ERFAErrorCode::ERROR_OK:
return "OK";
case MccCCTE_ERFAErrorCode::ERROR_NULLPTR:
return "input argument is the nullptr";
case MccCCTE_ERFAErrorCode::ERROR_INVALID_INPUT_ARG:
return "invalid argument";
case MccCCTE_ERFAErrorCode::ERROR_julday_INVALID_YEAR:
return "invalid year number";
case MccCCTE_ERFAErrorCode::ERROR_julday_INVALID_MONTH:
return "invalid month number";
case MccCCTE_ERFAErrorCode::ERROR_julday_INVALID_DAY:
return "invalid day number";
case MccCCTE_ERFAErrorCode::ERROR_UNSUPPORTED_COORD_PAIR:
return "unsupported coordinate pair";
case MccCCTE_ERFAErrorCode::ERROR_BULLETINA_OUT_OF_RANGE:
return "time point is out of range";
case MccCCTE_ERFAErrorCode::ERROR_LEAPSECONDS_OUT_OF_RANGE:
return "time point is out of range";
case MccCCTE_ERFAErrorCode::ERROR_DUBIOUS_YEAR:
return "dubious year";
case MccCCTE_ERFAErrorCode::ERROR_UNACCEPTABLE_DATE:
return "unacceptable date";
case MccCCTE_ERFAErrorCode::ERROR_UPDATE_LEAPSECONDS:
return "leap seconds update error";
case MccCCTE_ERFAErrorCode::ERROR_UPDATE_BULLETINA:
return "bulletin A update error";
case MccCCTE_ERFAErrorCode::ERROR_UNEXPECTED:
return "unexpected error value";
default:
return "UNKNOWN";
}
}
static const MccCCTE_ERFACategory& get()
{
static const MccCCTE_ERFACategory constInst;
return constInst;
}
};
inline std::error_code make_error_code(MccCCTE_ERFAErrorCode ec)
{
return std::error_code(static_cast<int>(ec), MccCCTE_ERFACategory::get());
}
class MccCCTE_ERFA
{
static constexpr double PI_2 = std::numbers::pi / 2.0;
public:
static constexpr double DEFAULT_WAVELENGTH = 0.55; // default observed wavelength in mkm
typedef std::error_code error_t;
struct refract_model_t {
static constexpr std::string_view name()
{
return "ERFA";
}
double refa, refb;
};
// meteo parameters (to compute refraction)
struct meteo_t {
typedef double temp_t;
typedef double humid_t;
typedef double press_t;
temp_t temperature; // Temperature in C
humid_t humidity; // humidity in % ([0.0, 1.0])
press_t pressure; // atmospheric presure in hPa=mB
};
// celestial object addition parameters
struct obj_pars_t {
double pm_RA = 0.0; // rads/year
double pm_DEC = 0.0; // rads/year
double parallax; // in arcsecs
double radvel; // radial velocity (signed, km/s)
};
struct engine_state_t {
meteo_t meteo{.temperature = 0.0, .humidity = 0.5, .pressure = 1010.0};
double wavelength = DEFAULT_WAVELENGTH; // observed wavelength in mkm
double lat = 0.0; // site latitude
double lon = 0.0; // site longitude
double elev = 0.0; // site elevation (in meters)
mcc::ccte::iers::MccLeapSeconds _leapSeconds{};
mcc::ccte::iers::MccIersBulletinA _bulletinA{};
};
MccCCTE_ERFA() : _stateMutex(new std::mutex) {}
MccCCTE_ERFA(engine_state_t state) : _currentState(std::move(state)), _stateMutex(new std::mutex) {}
MccCCTE_ERFA(const MccCCTE_ERFA&) = delete;
MccCCTE_ERFA& operator=(const MccCCTE_ERFA&) = delete;
MccCCTE_ERFA(MccCCTE_ERFA&&) = default;
MccCCTE_ERFA& operator=(MccCCTE_ERFA&&) = default;
virtual ~MccCCTE_ERFA() = default;
std::string_view nameCCTE() const
{
return "ERFA-CCTE-ENGINE";
}
// engine state related methods
void setStateERFA(engine_state_t state)
{
std::lock_guard lock{*_stateMutex};
_currentState = std::move(state);
}
engine_state_t getStateERFA() const
{
std::lock_guard lock{*_stateMutex};
return _currentState;
}
void updateMeteoERFA(meteo_t meteo)
{
std::lock_guard lock{*_stateMutex};
_currentState.meteo = std::move(meteo);
}
error_t updateLeapSeconds(std::derived_from<std::basic_istream<char>> auto& stream, char comment_sym = '#')
{
std::lock_guard lock{*_stateMutex};
if (!_currentState._leapSeconds.load(stream, comment_sym)) {
return MccCCTE_ERFAErrorCode::ERROR_UPDATE_LEAPSECONDS;
}
return MccCCTE_ERFAErrorCode::ERROR_OK;
}
error_t updateLeapSeconds(traits::mcc_input_char_range auto const& filename, char comment_sym = '#')
{
std::lock_guard lock{*_stateMutex};
if (!_currentState._leapSeconds.load(filename, comment_sym)) {
return MccCCTE_ERFAErrorCode::ERROR_UPDATE_LEAPSECONDS;
}
return MccCCTE_ERFAErrorCode::ERROR_OK;
}
error_t updateBulletinA(std::derived_from<std::basic_istream<char>> auto& stream, char comment_sym = '*')
{
std::lock_guard lock{*_stateMutex};
if (!_currentState._bulletinA.load(stream, comment_sym)) {
return MccCCTE_ERFAErrorCode::ERROR_UPDATE_BULLETINA;
}
return MccCCTE_ERFAErrorCode::ERROR_OK;
}
error_t updateBulletinA(traits::mcc_input_char_range auto const& filename, char comment_sym = '*')
{
std::lock_guard lock{*_stateMutex};
if (!_currentState._bulletinA.load(filename, comment_sym)) {
return MccCCTE_ERFAErrorCode::ERROR_UPDATE_BULLETINA;
}
return MccCCTE_ERFAErrorCode::ERROR_OK;
}
// apparent sideral time (Greenwitch or local)
error_t apparentSideralTime(mcc_coord_epoch_c auto const& epoch, mcc_angle_c auto* st, bool islocal = false)
{
error_t ret = MccCCTE_ERFAErrorCode::ERROR_OK;
if (st == nullptr) {
return MccCCTE_ERFAErrorCode::ERROR_NULLPTR;
}
using real_days_t = std::chrono::duration<double, std::ratio<86400>>;
double ut1 = epoch.MJD();
double tt = epoch.MJD();
std::lock_guard lock{*_stateMutex};
auto dut1 = _currentState._bulletinA.DUT1(epoch.MJD());
if (dut1.has_value()) {
ut1 += std::chrono::duration_cast<real_days_t>(dut1.value()).count();
} else { // out of range
return MccCCTE_ERFAErrorCode::ERROR_BULLETINA_OUT_OF_RANGE;
}
auto tai_utc = _currentState._leapSeconds[epoch.MJD()];
if (tai_utc.has_value()) {
tt += std::chrono::duration_cast<real_days_t>(tai_utc.value()).count();
} else {
return MccCCTE_ERFAErrorCode::ERROR_LEAPSECONDS_OUT_OF_RANGE;
}
auto tt_tai = _currentState._bulletinA.TT_TAI();
tt += std::chrono::duration_cast<real_days_t>(tt_tai).count();
*st = eraGst06a(ERFA_DJM0, ut1, ERFA_DJM0, tt);
if (islocal) {
*st = eraAnp(*st + _currentState.lon);
}
return ret;
}
// ICRS to observed
// returned azimuth is counted from the South through the West
error_t icrsToObs(mcc_angle_c auto const& ra_icrs,
mcc_angle_c auto const& dec_icrs,
mcc_coord_epoch_c auto const& epoch,
mcc_angle_c auto* ra_obs,
mcc_angle_c auto* dec_obs,
mcc_angle_c auto* ha_obs,
mcc_angle_c auto* az,
mcc_angle_c auto* zd,
obj_pars_t* obj_params = nullptr)
{
return icrsTo(true, ra_icrs, dec_icrs, epoch, ra_obs, dec_obs, ha_obs, az, zd, obj_params);
}
// error_t icrsToObs(MccSkyRADEC_ICRS const& radec_icrs,
// MccSkyRADEC_OBS* radec_obs,
// MccSkyAZZD* azzd,
// mcc_angle_c auto* ha_obs,
// obj_pars_t* obj_params = nullptr)
// {
// double ra_obs, dec_obs, az, zd, ha;
// auto err =
// icrsToObs(radec_icrs.x(), radec_icrs.y(), radec_icrs.epoch(), &ra_obs, &dec_obs, &ha, &az, &zd,
// obj_params);
// if (!err) {
// if (radec_obs) {
// radec_obs->setX(ra_obs);
// radec_obs->setY(dec_obs);
// }
// if (azzd) {
// azzd->setEpoch(radec_obs->epoch());
// azzd->setX(az);
// azzd->setY(zd);
// }
// if (ha_obs) {
// *ha_obs = ha;
// }
// }
// return err;
// };
// ICRS to apparent (in vacuo)
// returned azimuth is counted from the South through the West
error_t icrsToApp(mcc_angle_c auto const& ra_icrs,
mcc_angle_c auto const& dec_icrs,
mcc_coord_epoch_c auto const& epoch,
mcc_angle_c auto* ra_app,
mcc_angle_c auto* dec_app,
mcc_angle_c auto* ha_app,
mcc_angle_c auto* az,
mcc_angle_c auto* zd, // should be interpretated as zenithal distance corrected for refraction
obj_pars_t* obj_params = nullptr)
{
return icrsTo(false, ra_icrs, dec_icrs, epoch, ra_app, dec_app, ha_app, az, zd, obj_params);
}
// error_t icrsToApp(MccSkyRADEC_ICRS const& radec_icrs,
// MccSkyRADEC_OBS* radec_app,
// MccSkyAZZD* azzd,
// mcc_angle_c auto* ha_app,
// obj_pars_t* obj_params = nullptr)
// {
// double ra_app, dec_app, az, zd, ha;
// auto err =
// icrsToApp(radec_icrs.x(), radec_icrs.y(), radec_icrs.epoch(), &ra_app, &dec_app, &ha, &az, &zd,
// obj_params);
// if (!err) {
// if (radec_app) {
// radec_app->setX(ra_app);
// radec_app->setY(dec_app);
// }
// if (azzd) {
// azzd->setEpoch(radec_app->epoch());
// azzd->setX(az);
// azzd->setY(zd);
// }
// if (ha_app) {
// *ha_app = ha;
// }
// }
// return err;
// }
error_t obsToICRS(MccCoordPairKind obs_type,
mcc_coord_epoch_c auto const& epoch,
mcc_angle_c auto const& co_lon,
mcc_angle_c auto const& co_lat,
mcc_angle_c auto* ra_icrs,
mcc_angle_c auto* dec_icrs)
{
return toICRS(true, obs_type, epoch, co_lon, co_lat, ra_icrs, dec_icrs);
}
// error_t obsToICRS(mcc_coord_pair_c auto const& xy_obs, MccSkyRADEC_ICRS* radec_icrs)
// {
// double ra, dec;
// auto err = obsToICRS(xy_obs.pair_kind, xy_obs.epoch(), xy_obs.x(), xy_obs.y(), &ra, &dec);
// if (err) {
// return err;
// }
// if (radec_icrs) {
// radec_icrs->setX(ra);
// radec_icrs->setY(dec);
// }
// return err;
// }
error_t appToICRS(MccCoordPairKind app_type,
mcc_coord_epoch_c auto const& epoch,
mcc_angle_c auto const& co_lon,
mcc_angle_c auto const& co_lat,
mcc_angle_c auto* ra_icrs,
mcc_angle_c auto* dec_icrs)
{
return toICRS(false, app_type, epoch, co_lon, co_lat, ra_icrs, dec_icrs);
}
// error_t appToICRS(mcc_coord_pair_c auto const& xy_app, MccSkyRADEC_ICRS* radec_icrs)
// {
// double ra, dec;
// auto err = appToICRS(xy_app.pair_kind, xy_app.epoch(), xy_app.x(), xy_app.y(), &ra, &dec);
// if (!err) {
// if (radec_icrs) {
// radec_icrs->setX(ra);
// radec_icrs->setY(dec);
// }
// }
// return err;
// }
error_t equationOrigins(mcc_coord_epoch_c auto const& epoch, mcc_angle_c auto* eo)
{
if (eo == nullptr) {
return MccCCTE_ERFAErrorCode::ERROR_NULLPTR;
}
error_t ret = MccCCTE_ERFAErrorCode::ERROR_OK;
std::lock_guard lock{*_stateMutex};
using real_days_t = std::chrono::duration<double, std::ratio<86400>>;
double mjd = epoch.MJD();
auto tai_utc = _currentState._leapSeconds[mjd];
if (tai_utc.has_value()) {
double tt = mjd;
tt += std::chrono::duration_cast<real_days_t>(tai_utc.value()).count();
auto tt_tai = _currentState._bulletinA.TT_TAI();
tt += +std::chrono::duration_cast<real_days_t>(tt_tai).count();
*eo = eraEo06a(ERFA_DJM0, tt);
} else {
ret = MccCCTE_ERFAErrorCode::ERROR_LEAPSECONDS_OUT_OF_RANGE;
}
return ret;
}
// refraction
error_t refractionModel(refract_model_t* model)
{
if (model == nullptr) {
return MccCCTE_ERFAErrorCode::ERROR_NULLPTR;
}
std::lock_guard lock{*_stateMutex};
eraRefco(_currentState.meteo.pressure, _currentState.meteo.temperature, _currentState.meteo.humidity,
_currentState.wavelength, &model->refa, &model->refb);
return MccCCTE_ERFAErrorCode::ERROR_OK;
}
// Zobs must be observed zenithal distance (Zapp = Zobs + dZ -- corrected (in vacuo) zenithal distance)
template <typename ZAPP_T = std::nullptr_t>
error_t refractionCorrection(mcc_angle_c auto Zobs, mcc_angle_c auto* dZ, ZAPP_T Zapp = nullptr)
requires(std::is_null_pointer_v<ZAPP_T> ||
(std::is_pointer_v<ZAPP_T> && mcc_angle_c<std::remove_pointer_t<ZAPP_T>>))
{
error_t ret = MccCCTE_ERFAErrorCode::ERROR_OK;
if (dZ == nullptr) {
return MccCCTE_ERFAErrorCode::ERROR_NULLPTR;
}
refract_model_t rmodel;
ret = refractionModel(&rmodel);
if (!ret) {
ret = refractionCorrection(rmodel, Zobs, dZ, Zapp);
}
return ret;
}
// Zobs must be observed zenithal distance (Zapp = Zobs + dZ -- corrected (in vacuo) zenithal distance)
template <typename ZAPP_T = std::nullptr_t>
error_t refractionCorrection(const refract_model_t& rmodel,
mcc_angle_c auto Zobs,
mcc_angle_c auto* dZ,
ZAPP_T Zapp = nullptr)
requires(std::is_null_pointer_v<ZAPP_T> ||
(std::is_pointer_v<ZAPP_T> && mcc_angle_c<std::remove_pointer_t<ZAPP_T>>))
{
error_t ret = MccCCTE_ERFAErrorCode::ERROR_OK;
if (dZ == nullptr) {
return MccCCTE_ERFAErrorCode::ERROR_NULLPTR;
}
if (Zobs >= std::numbers::pi / 2.0) {
*dZ = 35.4 / 60.0 * std::numbers::pi / 180.0; // 35.4 arcminutes
} else {
auto tanZ = tan(Zobs);
*dZ = rmodel.refa * tanZ + rmodel.refb * tanZ * tanZ * tanZ;
}
if constexpr (!std::is_null_pointer_v<ZAPP_T>) {
*Zapp = Zobs + *dZ;
}
return ret;
}
// Zapp must be topocentric (in vacuo) zenithal distance (Zobs = Zapp - dZ -- observed, i.e. affected by refraction,
// zenithal distance)
template <typename ZOBS_T = std::nullptr_t>
error_t refractionReverseCorrection(mcc_angle_c auto Zapp, mcc_angle_c auto* dZ, ZOBS_T Zobs = nullptr)
requires(std::is_null_pointer_v<ZOBS_T> ||
(std::is_pointer_v<ZOBS_T> && mcc_angle_c<std::remove_pointer_t<ZOBS_T>>))
{
error_t ret = MccCCTE_ERFAErrorCode::ERROR_OK;
if (dZ == nullptr) {
return MccCCTE_ERFAErrorCode::ERROR_NULLPTR;
}
refract_model_t rmodel;
ret = refractionModel(&rmodel);
if (!ret) {
ret = refractionReverseCorrection(rmodel, Zapp, dZ, Zobs);
}
return ret;
}
// Zapp must be topocentric (in vacuo) zenithal distance (Zobs = Zapp - dZ -- observed, i.e. affected by refraction,
// zenithal distance)
template <typename ZOBS_T = std::nullptr_t>
error_t refractionReverseCorrection(const refract_model_t& rmodel,
mcc_angle_c auto Zapp,
mcc_angle_c auto* dZ,
ZOBS_T Zobs = nullptr)
requires(std::is_null_pointer_v<ZOBS_T> ||
(std::is_pointer_v<ZOBS_T> && mcc_angle_c<std::remove_pointer_t<ZOBS_T>>))
{
error_t ret = MccCCTE_ERFAErrorCode::ERROR_OK;
if (dZ == nullptr) {
return MccCCTE_ERFAErrorCode::ERROR_NULLPTR;
}
if (Zapp >= std::numbers::pi / 2.0) {
*dZ = 35.4 / 60.0 * std::numbers::pi / 180.0; // 35.4 arcminutes
} else {
auto tanZ = tan(Zapp);
auto tanZ2 = tanZ * tanZ;
auto b3 = 3.0 * rmodel.refb;
// with Newton-Raphson correction
*dZ = (rmodel.refa * tanZ + rmodel.refb * tanZ * tanZ2) /
(1.0 + rmodel.refa + tanZ2 * (rmodel.refa + b3) + b3 * tanZ2 * tanZ2);
}
if constexpr (!std::is_null_pointer_v<ZOBS_T>) {
*Zobs = Zapp - *dZ;
}
return ret;
}
/* helper mathods */
auto leapSecondsExpireDate() const
{
return _currentState._leapSeconds.expireDate();
}
auto leapSecondsExpireMJD() const
{
return _currentState._leapSeconds.expireMJD();
}
auto bulletinADateRange() const
{
return _currentState._bulletinA.dateRange();
}
auto bulletinADateRangeMJD() const
{
return _currentState._bulletinA.dateRangeMJD();
}
protected:
engine_state_t _currentState{};
std::unique_ptr<std::mutex> _stateMutex;
error_t icrsTo(bool observed, // true - observed, false - apparent
mcc_angle_c auto const& ra_icrs,
mcc_angle_c auto const& dec_icrs,
mcc_coord_epoch_c auto const& epoch,
mcc_angle_c auto* ra,
mcc_angle_c auto* dec,
mcc_angle_c auto* ha,
mcc_angle_c auto* az,
mcc_angle_c auto* zd,
obj_pars_t* obj_params = nullptr)
{
int err;
double r, d, h, a, z, eo;
double pressure = 0.0; // 0 for apparent coordinates type (see ERFA's refco.c: if pressure is zero then
// refraction is also zero)
error_t ret = MccCCTE_ERFAErrorCode::ERROR_OK;
std::lock_guard lock{*_stateMutex};
if (observed) {
pressure = _currentState.meteo.pressure;
}
auto dut1 = _currentState._bulletinA.DUT1(epoch.MJD());
if (!dut1.has_value()) {
return MccCCTE_ERFAErrorCode::ERROR_BULLETINA_OUT_OF_RANGE;
}
auto pol_pos = _currentState._bulletinA.polarCoords(epoch.MJD());
if (!pol_pos.has_value()) {
return MccCCTE_ERFAErrorCode::ERROR_BULLETINA_OUT_OF_RANGE;
}
// const auto arcsec2rad = std::numbers::pi / 180 / 3600;
const auto arcsec2rad = 1.0_arcsecs;
pol_pos->x *= arcsec2rad;
pol_pos->y *= arcsec2rad;
if (obj_params) {
err = eraAtco13(ra_icrs, dec_icrs, obj_params->pm_RA, obj_params->pm_DEC, obj_params->parallax,
obj_params->radvel, ERFA_DJM0, epoch.MJD(), dut1->count(), _currentState.lon,
_currentState.lat, _currentState.elev, pol_pos->x, pol_pos->y, pressure,
_currentState.meteo.temperature, _currentState.meteo.humidity, _currentState.wavelength, &a,
&z, &h, &d, &r, &eo);
} else {
err = eraAtco13(ra_icrs, dec_icrs, 0.0, 0.0, 0.0, 0.0, ERFA_DJM0, epoch.MJD(), dut1->count(),
_currentState.lon, _currentState.lat, _currentState.elev, pol_pos->x, pol_pos->y, pressure,
_currentState.meteo.temperature, _currentState.meteo.humidity, _currentState.wavelength, &a,
&z, &h, &d, &r, &eo);
}
if (err == 1) {
ret = MccCCTE_ERFAErrorCode::ERROR_DUBIOUS_YEAR;
} else if (err == -1) {
ret = MccCCTE_ERFAErrorCode::ERROR_UNACCEPTABLE_DATE;
}
if (ra) {
*ra = r;
}
if (dec) {
*dec = d;
}
if (ha) {
*ha = h;
}
if (az) {
// NOTE: according to definition of astronomical azimuth it is counted from the South through the West, but
// in the ERFA the azimuth is counted from the North through the East!!!
//
*az = MccAngle(a - std::numbers::pi).normalize<MccAngle::NORM_KIND_0_360>();
// *az = MccAngle(a + std::numbers::pi).normalize<MccAngle::NORM_KIND_0_360>();
}
if (zd) {
*zd = z;
}
return ret;
}
error_t toICRS(bool observed, // true - observed, false - apparent
MccCoordPairKind pair_type,
mcc_coord_epoch_c auto const& epoch,
mcc_angle_c auto const& co_lon,
mcc_angle_c auto const& co_lat,
mcc_angle_c auto* ra_icrs,
mcc_angle_c auto* dec_icrs)
{
error_t ret = MccCCTE_ERFAErrorCode::ERROR_OK;
// check coordinate pair consistency
if (mcc_is_app_coordpair(pair_type) && observed) {
return MccCCTE_ERFAErrorCode::ERROR_UNSUPPORTED_COORD_PAIR;
}
if (mcc_is_obs_coordpair(pair_type) && !observed) {
return MccCCTE_ERFAErrorCode::ERROR_UNSUPPORTED_COORD_PAIR;
}
std::lock_guard lock{*_stateMutex};
auto dut1 = _currentState._bulletinA.DUT1(epoch.MJD());
if (!dut1.has_value()) {
return MccCCTE_ERFAErrorCode::ERROR_BULLETINA_OUT_OF_RANGE;
}
auto pol_pos = _currentState._bulletinA.polarCoords(epoch.MJD());
if (!pol_pos.has_value()) {
return MccCCTE_ERFAErrorCode::ERROR_BULLETINA_OUT_OF_RANGE;
}
// const auto arcsec2rad = std::numbers::pi / 180 / 3600;
const auto arcsec2rad = 1.0_arcsecs;
pol_pos->x *= arcsec2rad;
pol_pos->y *= arcsec2rad;
std::string type;
double x, y, ra, dec;
double pressure = 0.0;
if (observed) {
pressure = _currentState.meteo.pressure;
}
switch (pair_type) {
case mcc::MccCoordPairKind::COORDS_KIND_AZZD:
// NOTE: according to definition of astronomical azimuth it is counted from the South through the West,
// but in the ERFA the azimuth is counted from the North through the East!!!
//
x = co_lon + std::numbers::pi;
y = co_lat;
type = "A";
break;
case mcc::MccCoordPairKind::COORDS_KIND_AZALT:
// NOTE: according to definition of astronomical azimuth it is counted from the South through the West,
// but in the ERFA the azimuth is counted from the North through the East!!!
//
x = co_lon + std::numbers::pi;
y = MccCCTE_ERFA::PI_2 - co_lat; // altitude to zenithal distance
type = "A";
break;
case mcc::MccCoordPairKind::COORDS_KIND_HADEC_OBS:
type = "H";
x = co_lon;
y = co_lat;
break;
case mcc::MccCoordPairKind::COORDS_KIND_RADEC_OBS:
type = "R";
x = co_lon;
y = co_lat;
break;
case mcc::MccCoordPairKind::COORDS_KIND_HADEC_APP:
type = "H";
x = co_lon;
y = co_lat;
break;
case mcc::MccCoordPairKind::COORDS_KIND_RADEC_APP:
type = "R";
x = co_lon;
y = co_lat;
break;
default:
return MccCCTE_ERFAErrorCode::ERROR_UNSUPPORTED_COORD_PAIR;
};
int err =
eraAtoc13(type.c_str(), x, y, ERFA_DJM0, epoch.MJD(), dut1->count(), _currentState.lon, _currentState.lat,
_currentState.elev, pol_pos->x, pol_pos->y, pressure, _currentState.meteo.temperature,
_currentState.meteo.humidity, _currentState.wavelength, &ra, &dec);
if (err == 1) {
ret = MccCCTE_ERFAErrorCode::ERROR_DUBIOUS_YEAR;
} else if (err == -1) {
ret = MccCCTE_ERFAErrorCode::ERROR_UNACCEPTABLE_DATE;
}
if (ra) {
*ra_icrs = ra;
}
if (dec) {
*dec_icrs = dec;
}
return ret;
}
};
} // namespace mcc::ccte::erfa

View File

@@ -15,10 +15,10 @@ static std::string MCC_DEFAULT_LEAP_SECONDS_FILE = R"--(
# Value of TAI-UTC in second valid beetween the initial value until # Value of TAI-UTC in second valid beetween the initial value until
# the epoch given on the next line. The last line reads that NO # the epoch given on the next line. The last line reads that NO
# leap second was introduced since the corresponding date # leap second was introduced since the corresponding date
# Updated through IERS Bulletin 70 issued in July 2025 # Updated through IERS Bulletin 71 issued in January 2026
# #
# #
# File expires on 28 June 2026 # File expires on 28 December 2026
# #
# #
# MJD Date TAI-UTC (s) # MJD Date TAI-UTC (s)
@@ -67,7 +67,7 @@ static std::string MCC_DEFAULT_IERS_BULLETIN_A_FILE = R"--(
* * * *
* Rapid Service/Prediction of Earth Orientation * * Rapid Service/Prediction of Earth Orientation *
********************************************************************** **********************************************************************
30 October 2025 Vol. XXXVIII No. 044 8 January 2026 Vol. XXXIX No. 002
______________________________________________________________________ ______________________________________________________________________
GENERAL INFORMATION: GENERAL INFORMATION:
MJD = Julian Date - 2 400 000.5 days MJD = Julian Date - 2 400 000.5 days
@@ -83,7 +83,7 @@ static std::string MCC_DEFAULT_IERS_BULLETIN_A_FILE = R"--(
* ANNOUNCEMENTS: * * ANNOUNCEMENTS: *
* * * *
* There will NOT be a leap second introduced in UTC * * There will NOT be a leap second introduced in UTC *
* at the end of December 2025. * * at the end of June 2026. *
* * * *
* The primary source for IERS Rapid Service/Prediction Center (RS/PC) * * The primary source for IERS Rapid Service/Prediction Center (RS/PC) *
* data products is the official IERS RS/PC website: * * data products is the official IERS RS/PC website: *
@@ -116,13 +116,47 @@ static std::string MCC_DEFAULT_IERS_BULLETIN_A_FILE = R"--(
IERS Rapid Service IERS Rapid Service
MJD x error y error UT1-UTC error MJD x error y error UT1-UTC error
" " " " s s " " " " s s
25 10 24 60972 0.18970 .00009 0.32280 .00009 0.093314 0.000025 26 1 2 61042 0.10962 .00009 0.33249 .00009 0.074152 0.000020
25 10 25 60973 0.18784 .00009 0.32283 .00009 0.093517 0.000025 26 1 3 61043 0.10827 .00009 0.33355 .00009 0.074367 0.000021
25 10 26 60974 0.18602 .00009 0.32275 .00009 0.093749 0.000022 26 1 4 61044 0.10690 .00009 0.33455 .00009 0.074487 0.000020
25 10 27 60975 0.18446 .00009 0.32243 .00009 0.094060 0.000019 26 1 5 61045 0.10554 .00009 0.33551 .00009 0.074361 0.000015
25 10 28 60976 0.18324 .00009 0.32200 .00009 0.094375 0.000016 26 1 6 61046 0.10404 .00009 0.33628 .00009 0.073991 0.000015
25 10 29 60977 0.18207 .00009 0.32173 .00009 0.094458 0.000016 26 1 7 61047 0.10253 .00009 0.33692 .00009 0.073470 0.000012
25 10 30 60978 0.18080 .00009 0.32157 .00009 0.094295 0.000016 26 1 8 61048 0.10121 .00009 0.33746 .00009 0.072861 0.000008
IERS Final Values
MJD x y UT1-UTC
" " s
25 11 2 60981 0.1750 0.3194 0.09220
25 11 3 60982 0.1735 0.3187 0.09112
25 11 4 60983 0.1718 0.3187 0.09005
25 11 5 60984 0.1699 0.3183 0.08916
25 11 6 60985 0.1679 0.3182 0.08854
25 11 7 60986 0.1659 0.3179 0.08822
25 11 8 60987 0.1640 0.3171 0.08814
25 11 9 60988 0.1630 0.3163 0.08819
25 11 10 60989 0.1625 0.3158 0.08820
25 11 11 60990 0.1615 0.3153 0.08801
25 11 12 60991 0.1602 0.3151 0.08763
25 11 13 60992 0.1583 0.3154 0.08711
25 11 14 60993 0.1564 0.3157 0.08644
25 11 15 60994 0.1545 0.3160 0.08566
25 11 16 60995 0.1530 0.3163 0.08494
25 11 17 60996 0.1511 0.3167 0.08433
25 11 18 60997 0.1491 0.3165 0.08390
25 11 19 60998 0.1474 0.3163 0.08366
25 11 20 60999 0.1456 0.3161 0.08357
25 11 21 61000 0.1437 0.3157 0.08363
25 11 22 61001 0.1419 0.3149 0.08381
25 11 23 61002 0.1398 0.3143 0.08403
25 11 24 61003 0.1380 0.3136 0.08426
25 11 25 61004 0.1373 0.3137 0.08431
25 11 26 61005 0.1360 0.3146 0.08417
25 11 27 61006 0.1351 0.3149 0.08377
25 11 28 61007 0.1343 0.3153 0.08305
25 11 29 61008 0.1334 0.3153 0.08209
25 11 30 61009 0.1325 0.3157 0.08097
25 12 1 61010 0.1316 0.3160 0.07984
_______________________________________________________________________ _______________________________________________________________________
@@ -130,403 +164,480 @@ static std::string MCC_DEFAULT_IERS_BULLETIN_A_FILE = R"--(
The following formulas will not reproduce the predictions given below, The following formulas will not reproduce the predictions given below,
but may be used to extend the predictions beyond the end of this table. but may be used to extend the predictions beyond the end of this table.
x = 0.1296 + 0.1193 cos A - 0.0805 sin A - 0.0668 cos C - 0.0019 sin C x = 0.1611 - 0.0626 cos A - 0.1221 sin A + 0.0025 cos C + 0.0585 sin C
y = 0.3859 - 0.0654 cos A - 0.1170 sin A - 0.0019 cos C + 0.0668 sin C y = 0.3836 - 0.1091 cos A + 0.0516 sin A + 0.0585 cos C - 0.0025 sin C
UT1-UTC = 0.0734 + 0.00008 (MJD - 60986) - (UT2-UT1) UT1-UTC = 0.0617 + 0.00005 (MJD - 61056) - (UT2-UT1)
where A = 2*pi*(MJD-60978)/365.25 and C = 2*pi*(MJD-60978)/435. where A = 2*pi*(MJD-61048)/365.25 and C = 2*pi*(MJD-61048)/435.
TAI-UTC(MJD 60979) = 37.0 TAI-UTC(MJD 61049) = 37.0
The accuracy may be estimated from the expressions: The accuracy may be estimated from the expressions:
S x,y = 0.00068 (MJD-60978)**0.80 S t = 0.00025 (MJD-60978)**0.75 S x,y = 0.00068 (MJD-61048)**0.80 S t = 0.00025 (MJD-61048)**0.75
Estimated accuracies are: Predictions 10 d 20 d 30 d 40 d Estimated accuracies are: Predictions 10 d 20 d 30 d 40 d
Polar coord's 0.004 0.007 0.010 0.013 Polar coord's 0.004 0.007 0.010 0.013
UT1-UTC 0.0014 0.0024 0.0032 0.0040 UT1-UTC 0.0014 0.0024 0.0032 0.0040
MJD x(arcsec) y(arcsec) UT1-UTC(sec) MJD x(arcsec) y(arcsec) UT1-UTC(sec)
2025 10 31 60979 0.1796 0.3213 0.09390 2026 1 9 61049 0.1001 0.3380 0.07228
2025 11 1 60980 0.1783 0.3211 0.09323 2026 1 10 61050 0.0993 0.3385 0.07180
2025 11 2 60981 0.1771 0.3209 0.09233 2026 1 11 61051 0.0985 0.3390 0.07149
2025 11 3 60982 0.1759 0.3207 0.09132 2026 1 12 61052 0.0978 0.3396 0.07136
2025 11 4 60983 0.1748 0.3204 0.09032 2026 1 13 61053 0.0971 0.3403 0.07142
2025 11 5 60984 0.1738 0.3201 0.08949 2026 1 14 61054 0.0964 0.3411 0.07168
2025 11 6 60985 0.1727 0.3198 0.08896 2026 1 15 61055 0.0957 0.3418 0.07209
2025 11 7 60986 0.1716 0.3195 0.08875 2026 1 16 61056 0.0949 0.3426 0.07260
2025 11 8 60987 0.1706 0.3193 0.08880 2026 1 17 61057 0.0942 0.3434 0.07313
2025 11 9 60988 0.1695 0.3190 0.08897 2026 1 18 61058 0.0935 0.3442 0.07360
2025 11 10 60989 0.1684 0.3187 0.08909 2026 1 19 61059 0.0928 0.3450 0.07391
2025 11 11 60990 0.1673 0.3184 0.08904 2026 1 20 61060 0.0921 0.3458 0.07400
2025 11 12 60991 0.1663 0.3181 0.08876 2026 1 21 61061 0.0915 0.3466 0.07380
2025 11 13 60992 0.1652 0.3178 0.08831 2026 1 22 61062 0.0909 0.3474 0.07332
2025 11 14 60993 0.1641 0.3176 0.08774 2026 1 23 61063 0.0903 0.3482 0.07264
2025 11 15 60994 0.1630 0.3173 0.08712 2026 1 24 61064 0.0897 0.3490 0.07185
2025 11 16 60995 0.1619 0.3170 0.08654 2026 1 25 61065 0.0892 0.3499 0.07110
2025 11 17 60996 0.1608 0.3168 0.08610 2026 1 26 61066 0.0886 0.3507 0.07051
2025 11 18 60997 0.1597 0.3166 0.08585 2026 1 27 61067 0.0881 0.3516 0.07015
2025 11 19 60998 0.1585 0.3163 0.08582 2026 1 28 61068 0.0876 0.3524 0.07004
2025 11 20 60999 0.1574 0.3161 0.08604 2026 1 29 61069 0.0872 0.3533 0.07016
2025 11 21 61000 0.1562 0.3159 0.08646 2026 1 30 61070 0.0867 0.3542 0.07040
2025 11 22 61001 0.1551 0.3157 0.08704 2026 1 31 61071 0.0863 0.3551 0.07062
2025 11 23 61002 0.1539 0.3156 0.08771 2026 2 1 61072 0.0858 0.3560 0.07069
2025 11 24 61003 0.1528 0.3154 0.08837 2026 2 2 61073 0.0855 0.3569 0.07052
2025 11 25 61004 0.1516 0.3153 0.08894 2026 2 3 61074 0.0851 0.3578 0.07011
2025 11 26 61005 0.1504 0.3152 0.08934 2026 2 4 61075 0.0847 0.3587 0.06952
2025 11 27 61006 0.1492 0.3151 0.08951 2026 2 5 61076 0.0844 0.3596 0.06884
2025 11 28 61007 0.1481 0.3150 0.08941 2026 2 6 61077 0.0841 0.3605 0.06818
2025 11 29 61008 0.1469 0.3149 0.08906 2026 2 7 61078 0.0838 0.3615 0.06763
2025 11 30 61009 0.1457 0.3149 0.08852 2026 2 8 61079 0.0835 0.3624 0.06723
2025 12 1 61010 0.1445 0.3148 0.08790 2026 2 9 61080 0.0832 0.3634 0.06700
2025 12 2 61011 0.1433 0.3148 0.08736 2026 2 10 61081 0.0830 0.3643 0.06696
2025 12 3 61012 0.1421 0.3148 0.08702 2026 2 11 61082 0.0828 0.3653 0.06706
2025 12 4 61013 0.1408 0.3149 0.08698 2026 2 12 61083 0.0826 0.3662 0.06727
2025 12 5 61014 0.1396 0.3149 0.08720 2026 2 13 61084 0.0824 0.3672 0.06753
2025 12 6 61015 0.1384 0.3150 0.08759 2026 2 14 61085 0.0822 0.3682 0.06774
2025 12 7 61016 0.1372 0.3151 0.08800 2026 2 15 61086 0.0821 0.3692 0.06784
2025 12 8 61017 0.1360 0.3152 0.08830 2026 2 16 61087 0.0820 0.3702 0.06774
2025 12 9 61018 0.1348 0.3153 0.08840 2026 2 17 61088 0.0819 0.3711 0.06738
2025 12 10 61019 0.1336 0.3155 0.08829 2026 2 18 61089 0.0818 0.3721 0.06675
2025 12 11 61020 0.1324 0.3157 0.08802 2026 2 19 61090 0.0818 0.3731 0.06590
2025 12 12 61021 0.1312 0.3159 0.08768 2026 2 20 61091 0.0817 0.3741 0.06493
2025 12 13 61022 0.1300 0.3161 0.08735 2026 2 21 61092 0.0817 0.3751 0.06395
2025 12 14 61023 0.1288 0.3163 0.08711 2026 2 22 61093 0.0817 0.3761 0.06311
2025 12 15 61024 0.1276 0.3166 0.08701 2026 2 23 61094 0.0818 0.3771 0.06250
2025 12 16 61025 0.1264 0.3169 0.08709 2026 2 24 61095 0.0818 0.3781 0.06215
2025 12 17 61026 0.1252 0.3172 0.08736 2026 2 25 61096 0.0819 0.3792 0.06203
2025 12 18 61027 0.1240 0.3175 0.08780 2026 2 26 61097 0.0820 0.3802 0.06204
2025 12 19 61028 0.1228 0.3178 0.08838 2026 2 27 61098 0.0821 0.3812 0.06206
2025 12 20 61029 0.1217 0.3182 0.08904 2026 2 28 61099 0.0823 0.3822 0.06196
2025 12 21 61030 0.1205 0.3185 0.08971 2026 3 1 61100 0.0824 0.3832 0.06165
2025 12 22 61031 0.1194 0.3189 0.09030 2026 3 2 61101 0.0826 0.3842 0.06111
2025 12 23 61032 0.1182 0.3194 0.09074 2026 3 3 61102 0.0828 0.3852 0.06037
2025 12 24 61033 0.1171 0.3198 0.09096 2026 3 4 61103 0.0831 0.3862 0.05951
2025 12 25 61034 0.1159 0.3202 0.09095 2026 3 5 61104 0.0833 0.3872 0.05864
2025 12 26 61035 0.1148 0.3207 0.09069 2026 3 6 61105 0.0836 0.3882 0.05787
2025 12 27 61036 0.1137 0.3212 0.09025 2026 3 7 61106 0.0839 0.3893 0.05726
2025 12 28 61037 0.1126 0.3217 0.08971 2026 3 8 61107 0.0842 0.3903 0.05686
2025 12 29 61038 0.1115 0.3223 0.08919 2026 3 9 61108 0.0846 0.3913 0.05667
2025 12 30 61039 0.1104 0.3228 0.08881 2026 3 10 61109 0.0849 0.3922 0.05667
2025 12 31 61040 0.1093 0.3234 0.08867 2026 3 11 61110 0.0853 0.3932 0.05681
2026 1 1 61041 0.1082 0.3240 0.08878 2026 3 12 61111 0.0857 0.3942 0.05703
2026 1 2 61042 0.1072 0.3246 0.08908 2026 3 13 61112 0.0861 0.3952 0.05725
2026 1 3 61043 0.1061 0.3252 0.08946 2026 3 14 61113 0.0866 0.3962 0.05737
2026 1 4 61044 0.1051 0.3258 0.08974 2026 3 15 61114 0.0870 0.3972 0.05732
2026 1 5 61045 0.1041 0.3265 0.08984 2026 3 16 61115 0.0875 0.3981 0.05703
2026 1 6 61046 0.1031 0.3272 0.08969 2026 3 17 61116 0.0880 0.3991 0.05647
2026 1 7 61047 0.1021 0.3278 0.08935 2026 3 18 61117 0.0886 0.4000 0.05564
2026 1 8 61048 0.1011 0.3286 0.08889 2026 3 19 61118 0.0891 0.4010 0.05462
2026 1 9 61049 0.1001 0.3293 0.08841 2026 3 20 61119 0.0897 0.4019 0.05355
2026 1 10 61050 0.0992 0.3300 0.08800 2026 3 21 61120 0.0903 0.4029 0.05256
2026 1 11 61051 0.0982 0.3308 0.08772 2026 3 22 61121 0.0909 0.4038 0.05180
2026 1 12 61052 0.0973 0.3316 0.08762 2026 3 23 61122 0.0916 0.4047 0.05132
2026 1 13 61053 0.0964 0.3323 0.08772 2026 3 24 61123 0.0922 0.4056 0.05111
2026 1 14 61054 0.0955 0.3331 0.08801 2026 3 25 61124 0.0929 0.4065 0.05107
2026 1 15 61055 0.0946 0.3340 0.08845 2026 3 26 61125 0.0936 0.4074 0.05108
2026 1 16 61056 0.0938 0.3348 0.08898 2026 3 27 61126 0.0943 0.4083 0.05100
2026 1 17 61057 0.0929 0.3357 0.08954 2026 3 28 61127 0.0950 0.4092 0.05075
2026 1 18 61058 0.0921 0.3365 0.09004 2026 3 29 61128 0.0958 0.4101 0.05028
2026 1 19 61059 0.0913 0.3374 0.09039 2026 3 30 61129 0.0966 0.4109 0.04959
2026 1 20 61060 0.0905 0.3383 0.09052 2026 3 31 61130 0.0974 0.4118 0.04876
2026 1 21 61061 0.0897 0.3392 0.09040 2026 4 1 61131 0.0982 0.4126 0.04787
2026 1 22 61062 0.0890 0.3401 0.09004 2026 4 2 61132 0.0990 0.4134 0.04702
2026 1 23 61063 0.0882 0.3411 0.08949 2026 4 3 61133 0.0998 0.4142 0.04629
2026 1 24 61064 0.0875 0.3420 0.08885 2026 4 4 61134 0.1007 0.4150 0.04574
2026 1 25 61065 0.0868 0.3430 0.08822 2026 4 5 61135 0.1016 0.4158 0.04540
2026 1 26 61066 0.0861 0.3439 0.08774 2026 4 6 61136 0.1025 0.4166 0.04526
2026 1 27 61067 0.0854 0.3449 0.08747 2026 4 7 61137 0.1034 0.4174 0.04528
2026 1 28 61068 0.0848 0.3459 0.08744 2026 4 8 61138 0.1044 0.4181 0.04540
2026 1 29 61069 0.0842 0.3469 0.08762 2026 4 9 61139 0.1053 0.4188 0.04556
2026 1 30 61070 0.0835 0.3479 0.08790 2026 4 10 61140 0.1063 0.4196 0.04566
2026 1 31 61071 0.0830 0.3490 0.08814 2026 4 11 61141 0.1073 0.4203 0.04563
2026 2 1 61072 0.0824 0.3500 0.08821 2026 4 12 61142 0.1083 0.4210 0.04539
2026 2 2 61073 0.0818 0.3511 0.08804 2026 4 13 61143 0.1093 0.4217 0.04489
2026 2 3 61074 0.0813 0.3521 0.08763 2026 4 14 61144 0.1103 0.4223 0.04411
2026 2 4 61075 0.0808 0.3532 0.08703 2026 4 15 61145 0.1114 0.4230 0.04311
2026 2 5 61076 0.0803 0.3543 0.08634 2026 4 16 61146 0.1124 0.4236 0.04198
2026 2 6 61077 0.0799 0.3554 0.08569 2026 4 17 61147 0.1135 0.4242 0.04088
2026 2 7 61078 0.0794 0.3565 0.08515 2026 4 18 61148 0.1146 0.4248 0.03995
2026 2 8 61079 0.0790 0.3576 0.08479 2026 4 19 61149 0.1157 0.4254 0.03929
2026 2 9 61080 0.0786 0.3587 0.08461 2026 4 20 61150 0.1168 0.4260 0.03893
2026 2 10 61081 0.0782 0.3598 0.08463 2026 4 21 61151 0.1180 0.4265 0.03881
2026 2 11 61082 0.0779 0.3609 0.08482 2026 4 22 61152 0.1191 0.4271 0.03879
2026 2 12 61083 0.0776 0.3621 0.08512 2026 4 23 61153 0.1203 0.4276 0.03874
2026 2 13 61084 0.0773 0.3632 0.08547 2026 4 24 61154 0.1214 0.4281 0.03855
2026 2 14 61085 0.0770 0.3643 0.08579 2026 4 25 61155 0.1226 0.4286 0.03815
2026 2 15 61086 0.0767 0.3655 0.08598 2026 4 26 61156 0.1238 0.4291 0.03757
2026 2 16 61087 0.0765 0.3666 0.08601 2026 4 27 61157 0.1250 0.4295 0.03685
2026 2 17 61088 0.0763 0.3678 0.08579 2026 4 28 61158 0.1262 0.4299 0.03606
2026 2 18 61089 0.0761 0.3690 0.08530 2026 4 29 61159 0.1275 0.4304 0.03532
2026 2 19 61090 0.0759 0.3701 0.08458 2026 4 30 61160 0.1287 0.4308 0.03468
2026 2 20 61091 0.0758 0.3713 0.08373 2026 5 1 61161 0.1300 0.4311 0.03422
2026 2 21 61092 0.0756 0.3725 0.08288 2026 5 2 61162 0.1312 0.4315 0.03397
2026 2 22 61093 0.0756 0.3737 0.08216 2026 5 3 61163 0.1325 0.4318 0.03394
2026 2 23 61094 0.0755 0.3748 0.08167 2026 5 4 61164 0.1338 0.4322 0.03410
2026 2 24 61095 0.0754 0.3760 0.08143 2026 5 5 61165 0.1350 0.4325 0.03439
2026 2 25 61096 0.0754 0.3772 0.08142 2026 5 6 61166 0.1363 0.4327 0.03475
2026 2 26 61097 0.0754 0.3784 0.08154 2026 5 7 61167 0.1376 0.4330 0.03509
2026 2 27 61098 0.0754 0.3796 0.08167 2026 5 8 61168 0.1389 0.4332 0.03536
2026 2 28 61099 0.0755 0.3808 0.08166 2026 5 9 61169 0.1402 0.4335 0.03547
2026 3 1 61100 0.0755 0.3820 0.08144 2026 5 10 61170 0.1416 0.4337 0.03538
2026 3 2 61101 0.0756 0.3832 0.08097 2026 5 11 61171 0.1429 0.4339 0.03505
2026 3 3 61102 0.0758 0.3844 0.08027 2026 5 12 61172 0.1442 0.4340 0.03452
2026 3 4 61103 0.0759 0.3855 0.07945 2026 5 13 61173 0.1456 0.4342 0.03383
2026 3 5 61104 0.0761 0.3867 0.07859 2026 5 14 61174 0.1469 0.4343 0.03311
2026 3 6 61105 0.0763 0.3879 0.07781 2026 5 15 61175 0.1482 0.4344 0.03249
2026 3 7 61106 0.0765 0.3891 0.07717 2026 5 16 61176 0.1496 0.4345 0.03211
2026 3 8 61107 0.0767 0.3903 0.07673 2026 5 17 61177 0.1509 0.4345 0.03204
2026 3 9 61108 0.0770 0.3915 0.07649 2026 5 18 61178 0.1523 0.4345 0.03225
2026 3 10 61109 0.0773 0.3926 0.07643 2026 5 19 61179 0.1537 0.4346 0.03262
2026 3 11 61110 0.0776 0.3938 0.07650 2026 5 20 61180 0.1550 0.4346 0.03301
2026 3 12 61111 0.0779 0.3950 0.07665 2026 5 21 61181 0.1564 0.4345 0.03328
2026 3 13 61112 0.0783 0.3961 0.07679 2026 5 22 61182 0.1577 0.4345 0.03336
2026 3 14 61113 0.0787 0.3973 0.07684 2026 5 23 61183 0.1591 0.4344 0.03325
2026 3 15 61114 0.0791 0.3984 0.07671 2026 5 24 61184 0.1605 0.4343 0.03299
2026 3 16 61115 0.0795 0.3996 0.07633 2026 5 25 61185 0.1618 0.4342 0.03268
2026 3 17 61116 0.0800 0.4007 0.07568 2026 5 26 61186 0.1632 0.4341 0.03241
2026 3 18 61117 0.0804 0.4019 0.07477 2026 5 27 61187 0.1646 0.4339 0.03224
2026 3 19 61118 0.0809 0.4030 0.07367 2026 5 28 61188 0.1659 0.4338 0.03225
2026 3 20 61119 0.0815 0.4041 0.07252 2026 5 29 61189 0.1673 0.4336 0.03246
2026 3 21 61120 0.0820 0.4052 0.07145 2026 5 30 61190 0.1686 0.4333 0.03289
2026 3 22 61121 0.0826 0.4063 0.07061 2026 5 31 61191 0.1700 0.4331 0.03352
2026 3 23 61122 0.0832 0.4074 0.07005 2026 6 1 61192 0.1713 0.4328 0.03429
2026 3 24 61123 0.0838 0.4085 0.06976 2026 6 2 61193 0.1727 0.4326 0.03515
2026 3 25 61124 0.0844 0.4096 0.06964 2026 6 3 61194 0.1740 0.4323 0.03602
2026 3 26 61125 0.0851 0.4107 0.06957 2026 6 4 61195 0.1754 0.4319 0.03683
2026 3 27 61126 0.0858 0.4118 0.06942 2026 6 5 61196 0.1767 0.4316 0.03751
2026 3 28 61127 0.0865 0.4128 0.06909 2026 6 6 61197 0.1781 0.4312 0.03801
2026 3 29 61128 0.0872 0.4139 0.06853 2026 6 7 61198 0.1794 0.4308 0.03831
2026 3 30 61129 0.0879 0.4149 0.06776 2026 6 8 61199 0.1807 0.4304 0.03841
2026 3 31 61130 0.0887 0.4159 0.06685 2026 6 9 61200 0.1820 0.4300 0.03835
2026 4 1 61131 0.0895 0.4169 0.06587 2026 6 10 61201 0.1833 0.4295 0.03823
2026 4 2 61132 0.0903 0.4179 0.06493 2026 6 11 61202 0.1846 0.4291 0.03815
2026 4 3 61133 0.0911 0.4189 0.06412 2026 6 12 61203 0.1859 0.4286 0.03824
2026 4 4 61134 0.0920 0.4199 0.06350 2026 6 13 61204 0.1872 0.4280 0.03860
2026 4 5 61135 0.0929 0.4208 0.06309 2026 6 14 61205 0.1885 0.4275 0.03924
2026 4 6 61136 0.0938 0.4218 0.06288 2026 6 15 61206 0.1897 0.4270 0.04009
2026 4 7 61137 0.0947 0.4227 0.06283 2026 6 16 61207 0.1910 0.4264 0.04101
2026 4 8 61138 0.0956 0.4237 0.06289 2026 6 17 61208 0.1922 0.4258 0.04185
2026 4 9 61139 0.0966 0.4246 0.06299 2026 6 18 61209 0.1935 0.4252 0.04250
2026 4 10 61140 0.0975 0.4255 0.06304 2026 6 19 61210 0.1947 0.4245 0.04293
2026 4 11 61141 0.0985 0.4263 0.06296 2026 6 20 61211 0.1959 0.4239 0.04317
2026 4 12 61142 0.0995 0.4272 0.06269 2026 6 21 61212 0.1971 0.4232 0.04333
2026 4 13 61143 0.1006 0.4281 0.06217 2026 6 22 61213 0.1983 0.4225 0.04352
2026 4 14 61144 0.1016 0.4289 0.06138 2026 6 23 61214 0.1995 0.4218 0.04381
2026 4 15 61145 0.1027 0.4297 0.06038 2026 6 24 61215 0.2007 0.4211 0.04428
2026 4 16 61146 0.1037 0.4305 0.05926 2026 6 25 61216 0.2018 0.4203 0.04496
2026 4 17 61147 0.1048 0.4313 0.05817 2026 6 26 61217 0.2030 0.4195 0.04586
2026 4 18 61148 0.1060 0.4321 0.05726 2026 6 27 61218 0.2041 0.4187 0.04695
2026 4 19 61149 0.1071 0.4328 0.05662 2026 6 28 61219 0.2052 0.4179 0.04820
2026 4 20 61150 0.1082 0.4335 0.05628 2026 6 29 61220 0.2063 0.4171 0.04955
2026 4 21 61151 0.1094 0.4343 0.05617 2026 6 30 61221 0.2074 0.4162 0.05092
2026 4 22 61152 0.1106 0.4350 0.05617 2026 7 1 61222 0.2085 0.4154 0.05224
2026 4 23 61153 0.1118 0.4356 0.05612 2026 7 2 61223 0.2095 0.4145 0.05344
2026 4 24 61154 0.1130 0.4363 0.05592 2026 7 3 61224 0.2106 0.4136 0.05447
2026 4 25 61155 0.1142 0.4370 0.05551 2026 7 4 61225 0.2116 0.4127 0.05530
2026 4 26 61156 0.1154 0.4376 0.05489 2026 7 5 61226 0.2126 0.4118 0.05595
2026 4 27 61157 0.1167 0.4382 0.05412 2026 7 6 61227 0.2136 0.4108 0.05644
2026 4 28 61158 0.1179 0.4388 0.05327 2026 7 7 61228 0.2146 0.4098 0.05686
2026 4 29 61159 0.1192 0.4393 0.05243 2026 7 8 61229 0.2155 0.4088 0.05730
2026 4 30 61160 0.1205 0.4399 0.05167 2026 7 9 61230 0.2164 0.4078 0.05787
2026 5 1 61161 0.1218 0.4404 0.05106 2026 7 10 61231 0.2174 0.4068 0.05866
2026 5 2 61162 0.1231 0.4409 0.05062 2026 7 11 61232 0.2183 0.4058 0.05971
2026 5 3 61163 0.1244 0.4414 0.05035 2026 7 12 61233 0.2191 0.4048 0.06099
2026 5 4 61164 0.1258 0.4419 0.05022 2026 7 13 61234 0.2200 0.4037 0.06238
2026 5 5 61165 0.1271 0.4423 0.05019 2026 7 14 61235 0.2208 0.4026 0.06374
2026 5 6 61166 0.1285 0.4428 0.05018 2026 7 15 61236 0.2217 0.4015 0.06490
2026 5 7 61167 0.1298 0.4432 0.05014 2026 7 16 61237 0.2225 0.4004 0.06580
2026 5 8 61168 0.1312 0.4436 0.04999 2026 7 17 61238 0.2232 0.3993 0.06643
2026 5 9 61169 0.1326 0.4439 0.04969 2026 7 18 61239 0.2240 0.3982 0.06686
2026 5 10 61170 0.1340 0.4443 0.04920 2026 7 19 61240 0.2247 0.3970 0.06720
2026 5 11 61171 0.1354 0.4446 0.04852 2026 7 20 61241 0.2255 0.3959 0.06757
2026 5 12 61172 0.1368 0.4449 0.04766 2026 7 21 61242 0.2262 0.3947 0.06804
2026 5 13 61173 0.1382 0.4452 0.04669 2026 7 22 61243 0.2268 0.3935 0.06865
2026 5 14 61174 0.1396 0.4454 0.04572 2026 7 23 61244 0.2275 0.3923 0.06942
2026 5 15 61175 0.1410 0.4457 0.04492 2026 7 24 61245 0.2281 0.3911 0.07035
2026 5 16 61176 0.1425 0.4459 0.04441 2026 7 25 61246 0.2287 0.3899 0.07139
2026 5 17 61177 0.1439 0.4461 0.04425 2026 7 26 61247 0.2293 0.3887 0.07253
2026 5 18 61178 0.1454 0.4462 0.04443 2026 7 27 61248 0.2299 0.3875 0.07369
2026 5 19 61179 0.1468 0.4464 0.04482 2026 7 28 61249 0.2304 0.3862 0.07483
2026 5 20 61180 0.1483 0.4465 0.04526 2026 7 29 61250 0.2309 0.3849 0.07589
2026 5 21 61181 0.1497 0.4466 0.04563 2026 7 30 61251 0.2314 0.3837 0.07681
2026 5 22 61182 0.1512 0.4467 0.04582 2026 7 31 61252 0.2319 0.3824 0.07755
2026 5 23 61183 0.1526 0.4468 0.04584 2026 8 1 61253 0.2323 0.3811 0.07812
2026 5 24 61184 0.1541 0.4468 0.04574 2026 8 2 61254 0.2327 0.3798 0.07854
2026 5 25 61185 0.1556 0.4468 0.04560 2026 8 3 61255 0.2331 0.3785 0.07889
2026 5 26 61186 0.1570 0.4468 0.04551 2026 8 4 61256 0.2335 0.3772 0.07925
2026 5 27 61187 0.1585 0.4468 0.04553 2026 8 5 61257 0.2338 0.3759 0.07972
2026 5 28 61188 0.1600 0.4467 0.04573 2026 8 6 61258 0.2342 0.3746 0.08039
2026 5 29 61189 0.1615 0.4466 0.04613 2026 8 7 61259 0.2344 0.3733 0.08130
2026 5 30 61190 0.1629 0.4465 0.04673 2026 8 8 61260 0.2347 0.3719 0.08241
2026 5 31 61191 0.1644 0.4464 0.04754 2026 8 9 61261 0.2350 0.3706 0.08365
2026 6 1 61192 0.1659 0.4462 0.04850 2026 8 10 61262 0.2352 0.3692 0.08486
2026 6 2 61193 0.1673 0.4461 0.04955 2026 8 11 61263 0.2354 0.3679 0.08590
2026 6 3 61194 0.1688 0.4459 0.05060 2026 8 12 61264 0.2355 0.3665 0.08667
2026 6 4 61195 0.1703 0.4456 0.05158 2026 8 13 61265 0.2357 0.3652 0.08710
2026 6 5 61196 0.1717 0.4454 0.05243 2026 8 14 61266 0.2358 0.3638 0.08724
2026 6 6 61197 0.1732 0.4451 0.05309 2026 8 15 61267 0.2359 0.3624 0.08725
2026 6 7 61198 0.1747 0.4448 0.05353 2026 8 16 61268 0.2359 0.3611 0.08727
2026 6 8 61199 0.1761 0.4445 0.05378 2026 8 17 61269 0.2360 0.3597 0.08741
2026 6 9 61200 0.1776 0.4442 0.05389 2026 8 18 61270 0.2360 0.3583 0.08772
2026 6 10 61201 0.1790 0.4438 0.05396 2026 8 19 61271 0.2360 0.3570 0.08823
2026 6 11 61202 0.1804 0.4435 0.05412 2026 8 20 61272 0.2359 0.3556 0.08889
2026 6 12 61203 0.1819 0.4431 0.05448 2026 8 21 61273 0.2359 0.3542 0.08975
2026 6 13 61204 0.1833 0.4426 0.05516 2026 8 22 61274 0.2358 0.3528 0.09075
2026 6 14 61205 0.1847 0.4422 0.05615 2026 8 23 61275 0.2356 0.3515 0.09173
2026 6 15 61206 0.1861 0.4417 0.05730 2026 8 24 61276 0.2355 0.3501 0.09267
2026 6 16 61207 0.1875 0.4412 0.05855 2026 8 25 61277 0.2353 0.3487 0.09354
2026 6 17 61208 0.1889 0.4407 0.05970 2026 8 26 61278 0.2351 0.3473 0.09418
2026 6 18 61209 0.1903 0.4402 0.06064 2026 8 27 61279 0.2349 0.3460 0.09450
2026 6 19 61210 0.1917 0.4396 0.06134 2026 8 28 61280 0.2346 0.3446 0.09461
2026 6 20 61211 0.1930 0.4391 0.06189 2026 8 29 61281 0.2344 0.3432 0.09454
2026 6 21 61212 0.1944 0.4385 0.06231 2026 8 30 61282 0.2340 0.3419 0.09433
2026 6 22 61213 0.1957 0.4378 0.06270 2026 8 31 61283 0.2337 0.3405 0.09408
2026 6 23 61214 0.1971 0.4372 0.06319 2026 9 1 61284 0.2334 0.3391 0.09392
2026 6 24 61215 0.1984 0.4365 0.06381 2026 9 2 61285 0.2330 0.3378 0.09389
2026 6 25 61216 0.1997 0.4359 0.06463 2026 9 3 61286 0.2326 0.3364 0.09406
2026 6 26 61217 0.2010 0.4352 0.06565 2026 9 4 61287 0.2321 0.3351 0.09437
2026 6 27 61218 0.2023 0.4344 0.06693 2026 9 5 61288 0.2317 0.3337 0.09483
2026 6 28 61219 0.2036 0.4337 0.06834 2026 9 6 61289 0.2312 0.3324 0.09533
2026 6 29 61220 0.2048 0.4329 0.06988 2026 9 7 61290 0.2306 0.3311 0.09572
2026 6 30 61221 0.2061 0.4321 0.07149 2026 9 8 61291 0.2301 0.3298 0.09587
2026 7 1 61222 0.2073 0.4313 0.07309 2026 9 9 61292 0.2295 0.3285 0.09576
2026 7 2 61223 0.2085 0.4305 0.07453 2026 9 10 61293 0.2289 0.3272 0.09541
2026 7 3 61224 0.2097 0.4297 0.07579 2026 9 11 61294 0.2283 0.3259 0.09492
2026 7 4 61225 0.2109 0.4288 0.07685 2026 9 12 61295 0.2277 0.3246 0.09452
2026 7 5 61226 0.2121 0.4279 0.07772 2026 9 13 61296 0.2270 0.3233 0.09420
2026 7 6 61227 0.2132 0.4270 0.07845 2026 9 14 61297 0.2263 0.3220 0.09410
2026 7 7 61228 0.2144 0.4261 0.07908 2026 9 15 61298 0.2256 0.3208 0.09416
2026 7 8 61229 0.2155 0.4252 0.07971 2026 9 16 61299 0.2248 0.3195 0.09433
2026 7 9 61230 0.2166 0.4242 0.08048 2026 9 17 61300 0.2241 0.3183 0.09465
2026 7 10 61231 0.2177 0.4233 0.08139 2026 9 18 61301 0.2233 0.3170 0.09510
2026 7 11 61232 0.2188 0.4223 0.08254 2026 9 19 61302 0.2225 0.3158 0.09562
2026 7 12 61233 0.2198 0.4213 0.08400 2026 9 20 61303 0.2216 0.3146 0.09615
2026 7 13 61234 0.2209 0.4203 0.08562 2026 9 21 61304 0.2208 0.3134 0.09657
2026 7 14 61235 0.2219 0.4192 0.08710 2026 9 22 61305 0.2199 0.3122 0.09690
2026 7 15 61236 0.2229 0.4182 0.08840 2026 9 23 61306 0.2190 0.3110 0.09704
2026 7 16 61237 0.2238 0.4171 0.08945 2026 9 24 61307 0.2180 0.3099 0.09702
2026 7 17 61238 0.2248 0.4160 0.09020 2026 9 25 61308 0.2171 0.3087 0.09681
2026 7 18 61239 0.2257 0.4149 0.09079 2026 9 26 61309 0.2161 0.3076 0.09642
2026 7 19 61240 0.2267 0.4138 0.09128 2026 9 27 61310 0.2151 0.3065 0.09591
2026 7 20 61241 0.2276 0.4127 0.09179 2026 9 28 61311 0.2141 0.3054 0.09537
2026 7 21 61242 0.2284 0.4115 0.09251 2026 9 29 61312 0.2130 0.3043 0.09501
2026 7 22 61243 0.2293 0.4104 0.09346 2026 9 30 61313 0.2119 0.3032 0.09489
2026 7 23 61244 0.2301 0.4092 0.09462 2026 10 1 61314 0.2109 0.3022 0.09500
2026 7 24 61245 0.2309 0.4080 0.09598 2026 10 2 61315 0.2097 0.3011 0.09526
2026 7 25 61246 0.2317 0.4068 0.09752 2026 10 3 61316 0.2086 0.3001 0.09568
2026 7 26 61247 0.2325 0.4056 0.09923 2026 10 4 61317 0.2075 0.2991 0.09602
2026 7 27 61248 0.2332 0.4044 0.10098 2026 10 5 61318 0.2063 0.2981 0.09613
2026 7 28 61249 0.2339 0.4031 0.10263 2026 10 6 61319 0.2051 0.2971 0.09602
2026 7 29 61250 0.2346 0.4019 0.10410 2026 10 7 61320 0.2039 0.2962 0.09573
2026 7 30 61251 0.2353 0.4006 0.10535 2026 10 8 61321 0.2027 0.2952 0.09526
2026 7 31 61252 0.2360 0.3994 0.10625 2026 10 9 61322 0.2014 0.2943 0.09470
2026 8 1 61253 0.2366 0.3981 0.10700 2026 10 10 61323 0.2001 0.2934 0.09420
2026 8 2 61254 0.2372 0.3968 0.10755 2026 10 11 61324 0.1989 0.2925 0.09382
2026 8 3 61255 0.2378 0.3955 0.10800 2026 10 12 61325 0.1976 0.2917 0.09360
2026 8 4 61256 0.2383 0.3942 0.10837 2026 10 13 61326 0.1962 0.2908 0.09354
2026 8 5 61257 0.2388 0.3929 0.10886 2026 10 14 61327 0.1949 0.2900 0.09367
2026 8 6 61258 0.2393 0.3916 0.10961 2026 10 15 61328 0.1935 0.2892 0.09395
2026 8 7 61259 0.2398 0.3902 0.11059 2026 10 16 61329 0.1922 0.2884 0.09436
2026 8 8 61260 0.2403 0.3889 0.11182 2026 10 17 61330 0.1908 0.2876 0.09484
2026 8 9 61261 0.2407 0.3876 0.11317 2026 10 18 61331 0.1894 0.2869 0.09525
2026 8 10 61262 0.2411 0.3862 0.11450 2026 10 19 61332 0.1880 0.2862 0.09550
2026 8 11 61263 0.2415 0.3848 0.11570 2026 10 20 61333 0.1865 0.2855 0.09558
2026 8 12 61264 0.2418 0.3835 0.11665 2026 10 21 61334 0.1851 0.2848 0.09543
2026 8 13 61265 0.2422 0.3821 0.11726 2026 10 22 61335 0.1836 0.2841 0.09503
2026 8 14 61266 0.2424 0.3807 0.11763 2026 10 23 61336 0.1821 0.2835 0.09443
2026 8 15 61267 0.2427 0.3793 0.11793 2026 10 24 61337 0.1807 0.2829 0.09372
2026 8 16 61268 0.2430 0.3780 0.11819 2026 10 25 61338 0.1792 0.2823 0.09295
2026 8 17 61269 0.2432 0.3766 0.11850 2026 10 26 61339 0.1776 0.2817 0.09229
2026 8 18 61270 0.2434 0.3752 0.11910 2026 10 27 61340 0.1761 0.2812 0.09186
2026 8 19 61271 0.2435 0.3738 0.12000 2026 10 28 61341 0.1746 0.2807 0.09174
2026 8 20 61272 0.2437 0.3724 0.12109 2026 10 29 61342 0.1730 0.2802 0.09188
2026 8 21 61273 0.2438 0.3710 0.12242 2026 10 30 61343 0.1715 0.2797 0.09217
2026 8 22 61274 0.2439 0.3696 0.12379 2026 10 31 61344 0.1699 0.2792 0.09250
2026 8 23 61275 0.2439 0.3682 0.12516 2026 11 1 61345 0.1683 0.2788 0.09272
2026 8 24 61276 0.2440 0.3668 0.12644 2026 11 2 61346 0.1667 0.2784 0.09267
2026 8 25 61277 0.2440 0.3654 0.12762 2026 11 3 61347 0.1651 0.2780 0.09243
2026 8 26 61278 0.2439 0.3639 0.12856 2026 11 4 61348 0.1635 0.2777 0.09199
2026 8 27 61279 0.2439 0.3625 0.12930 2026 11 5 61349 0.1619 0.2773 0.09144
2026 8 28 61280 0.2438 0.3611 0.12977 2026 11 6 61350 0.1603 0.2770 0.09088
2026 8 29 61281 0.2437 0.3597 0.13002 2026 11 7 61351 0.1587 0.2767 0.09046
2026 8 30 61282 0.2436 0.3583 0.13010 2026 11 8 61352 0.1571 0.2765 0.09016
2026 8 31 61283 0.2434 0.3569 0.13012 2026 11 9 61353 0.1554 0.2763 0.09004
2026 9 1 61284 0.2432 0.3555 0.13019 2026 11 10 61354 0.1538 0.2760 0.09011
2026 9 2 61285 0.2430 0.3541 0.13033 2026 11 11 61355 0.1521 0.2759 0.09033
2026 9 3 61286 0.2428 0.3527 0.13061 2026 11 12 61356 0.1505 0.2757 0.09067
2026 9 4 61287 0.2425 0.3513 0.13112 2026 11 13 61357 0.1488 0.2756 0.09106
2026 9 5 61288 0.2422 0.3499 0.13176 2026 11 14 61358 0.1471 0.2755 0.09148
2026 9 6 61289 0.2419 0.3486 0.13238 2026 11 15 61359 0.1455 0.2754 0.09178
2026 9 7 61290 0.2415 0.3472 0.13286 2026 11 16 61360 0.1438 0.2753 0.09198
2026 9 8 61291 0.2411 0.3458 0.13307 2026 11 17 61361 0.1421 0.2753 0.09203
2026 9 9 61292 0.2407 0.3444 0.13301 2026 11 18 61362 0.1405 0.2753 0.09194
2026 9 10 61293 0.2403 0.3431 0.13275 2026 11 19 61363 0.1388 0.2753 0.09161
2026 9 11 61294 0.2399 0.3417 0.13228 2026 11 20 61364 0.1371 0.2754 0.09115
2026 9 12 61295 0.2394 0.3404 0.13182 2026 11 21 61365 0.1354 0.2755 0.09063
2026 9 13 61296 0.2389 0.3390 0.13136 2026 11 22 61366 0.1338 0.2756 0.09016
2026 9 14 61297 0.2383 0.3377 0.13101 2026 11 23 61367 0.1321 0.2757 0.08988
2026 9 15 61298 0.2378 0.3364 0.13082 2026 11 24 61368 0.1304 0.2759 0.08982
2026 9 16 61299 0.2372 0.3351 0.13083 2026 11 25 61369 0.1287 0.2760 0.09000
2026 9 17 61300 0.2366 0.3338 0.13104 2026 11 26 61370 0.1271 0.2762 0.09038
2026 9 18 61301 0.2359 0.3325 0.13133 2026 11 27 61371 0.1254 0.2765 0.09073
2026 9 19 61302 0.2353 0.3312 0.13171 2026 11 28 61372 0.1237 0.2767 0.09096
2026 9 20 61303 0.2346 0.3299 0.13210 2026 11 29 61373 0.1221 0.2770 0.09110
2026 9 21 61304 0.2339 0.3286 0.13238 2026 11 30 61374 0.1204 0.2773 0.09103
2026 9 22 61305 0.2331 0.3274 0.13247 2026 12 1 61375 0.1188 0.2777 0.09070
2026 9 23 61306 0.2323 0.3261 0.13234 2026 12 2 61376 0.1171 0.2780 0.09027
2026 9 24 61307 0.2316 0.3249 0.13197 2026 12 3 61377 0.1155 0.2784 0.08987
2026 9 25 61308 0.2307 0.3237 0.13133 2026 12 4 61378 0.1139 0.2788 0.08951
2026 9 26 61309 0.2299 0.3225 0.13057 2026 12 5 61379 0.1122 0.2793 0.08932
2026 9 27 61310 0.2290 0.3213 0.12980 2026 12 6 61380 0.1106 0.2797 0.08929
2026 9 28 61311 0.2282 0.3201 0.12907 2026 12 7 61381 0.1090 0.2802 0.08941
2026 9 29 61312 0.2273 0.3189 0.12856 2026 12 8 61382 0.1074 0.2807 0.08977
2026 9 30 61313 0.2263 0.3178 0.12825 2026 12 9 61383 0.1058 0.2813 0.09031
2026 10 1 61314 0.2254 0.3167 0.12823 2026 12 10 61384 0.1042 0.2819 0.09092
2026 10 2 61315 0.2244 0.3155 0.12839 2026 12 11 61385 0.1026 0.2824 0.09153
2026 10 3 61316 0.2234 0.3144 0.12872 2026 12 12 61386 0.1011 0.2831 0.09210
2026 10 4 61317 0.2224 0.3133 0.12899 2026 12 13 61387 0.0995 0.2837 0.09261
2026 10 5 61318 0.2213 0.3123 0.12909 2026 12 14 61388 0.0980 0.2844 0.09296
2026 10 6 61319 0.2203 0.3112 0.12904 2026 12 15 61389 0.0964 0.2851 0.09308
2026 10 7 61320 0.2192 0.3101 0.12871 2026 12 16 61390 0.0949 0.2858 0.09298
2026 10 8 61321 0.2181 0.3091 0.12823 2026 12 17 61391 0.0934 0.2865 0.09271
2026 10 9 61322 0.2169 0.3081 0.12763 2026 12 18 61392 0.0919 0.2873 0.09223
2026 10 10 61323 0.2158 0.3071 0.12702 2026 12 19 61393 0.0904 0.2881 0.09183
2026 10 11 61324 0.2146 0.3061 0.12646 2026 12 20 61394 0.0889 0.2889 0.09151
2026 10 12 61325 0.2134 0.3052 0.12607 2026 12 21 61395 0.0875 0.2897 0.09140
2026 10 13 61326 0.2122 0.3043 0.12588 2026 12 22 61396 0.0860 0.2906 0.09146
2026 10 14 61327 0.2110 0.3033 0.12585 2026 12 23 61397 0.0846 0.2914 0.09172
2026 10 15 61328 0.2098 0.3024 0.12602 2026 12 24 61398 0.0832 0.2923 0.09217
2026 10 16 61329 0.2085 0.3016 0.12630 2026 12 25 61399 0.0818 0.2933 0.09256
2026 10 17 61330 0.2072 0.3007 0.12661 2026 12 26 61400 0.0804 0.2942 0.09281
2026 10 18 61331 0.2059 0.2999 0.12683 2026 12 27 61401 0.0791 0.2952 0.09278
2026 10 19 61332 0.2046 0.2990 0.12689 2026 12 28 61402 0.0777 0.2962 0.09251
2026 10 20 61333 0.2033 0.2982 0.12680 2026 12 29 61403 0.0764 0.2972 0.09213
2026 10 21 61334 0.2019 0.2975 0.12644 2026 12 30 61404 0.0751 0.2982 0.09173
2026 10 22 61335 0.2005 0.2967 0.12584 2026 12 31 61405 0.0738 0.2993 0.09140
2026 10 23 61336 0.1992 0.2960 0.12511 2027 1 1 61406 0.0725 0.3004 0.09123
2026 10 24 61337 0.1978 0.2952 0.12426 2027 1 2 61407 0.0713 0.3015 0.09132
2026 10 25 61338 0.1964 0.2946 0.12345 2027 1 3 61408 0.0700 0.3026 0.09160
2026 10 26 61339 0.1949 0.2939 0.12281 2027 1 4 61409 0.0688 0.3037 0.09200
2026 10 27 61340 0.1935 0.2932 0.12239 2027 1 5 61410 0.0676 0.3049 0.09264
2026 10 28 61341 0.1920 0.2926 0.12222 2027 1 6 61411 0.0665 0.3061 0.09344
2026 10 29 61342 0.1906 0.2920 0.12229 2027 1 7 61412 0.0653 0.3072 0.09426
2026 10 30 61343 0.1891 0.2914 0.12254 2027 1 8 61413 0.0642 0.3085 0.09509
These predictions are based on all announced leap seconds. These predictions are based on all announced leap seconds.
CELESTIAL POLE OFFSET SERIES: CELESTIAL POLE OFFSET SERIES:
NEOS Celestial Pole Offset Series NEOS Celestial Pole Offset Series
MJD dpsi error deps error MJD dpsi error deps error
(msec. of arc) (msec. of arc)
60959 -120.67 0.72 -9.76 0.02 61026 -114.45 1.33 -7.19 0.16
60960 -120.67 0.85 -9.67 0.05 61027 -114.43 1.33 -7.42 0.16
60961 -120.61 0.85 -9.74 0.05 61028 -114.48 1.33 -7.64 0.16
60962 -120.34 0.95 -9.85 0.06 61029 -114.51 1.19 -7.67 0.18
61030 -114.39 1.19 -7.49 0.18
61031 -114.09 1.24 -7.26 0.16
61032 -113.74 1.35 -7.13 0.06
61033 -113.52 1.35 -7.11 0.06
IERS Celestial Pole Offset Final Series
MJD dpsi deps
(msec. of arc)
60981 -117.6 -9.0
60982 -117.8 -8.8
60983 -118.0 -8.6
60984 -117.8 -8.6
60985 -117.5 -8.8
60986 -117.1 -8.9
60987 -116.8 -8.7
60988 -116.9 -8.5
60989 -117.0 -8.6
60990 -116.9 -8.8
60991 -116.6 -8.8
60992 -116.5 -8.5
60993 -116.5 -8.0
60994 -116.6 -7.7
60995 -116.6 -7.6
60996 -116.4 -7.5
60997 -116.1 -7.5
60998 -115.6 -7.8
60999 -115.5 -8.3
61000 -115.9 -8.6
61001 -116.0 -8.6
61002 -115.9 -8.5
61003 -115.7 -8.3
61004 -115.4 -8.2
61005 -115.1 -8.2
61006 -114.7 -8.1
61007 -114.4 -7.9
61008 -114.3 -7.9
61009 -114.6 -7.8
61010 -115.2 -7.6
IAU2000A Celestial Pole Offset Series IAU2000A Celestial Pole Offset Series
MJD dX error dY error MJD dX error dY error
(msec. of arc) (msec. of arc)
60959 0.295 0.287 -0.064 0.017 61026 0.512 0.531 -0.092 0.161
60960 0.308 0.339 -0.063 0.047 61027 0.519 0.531 -0.101 0.161
60961 0.323 0.339 -0.063 0.047 61028 0.523 0.531 -0.107 0.161
60962 0.340 0.379 -0.062 0.062 61029 0.527 0.474 -0.112 0.176
61030 0.530 0.474 -0.114 0.176
61031 0.533 0.494 -0.114 0.156
61032 0.536 0.536 -0.113 0.059
61033 0.539 0.536 -0.111 0.059
IAU2000A Celestial Pole Offset Final Series
MJD dX dY
(msec. of arc)
60981 0.40 0.03
60982 0.38 0.02
60983 0.37 0.00
60984 0.42 -0.05
60985 0.41 -0.05
60986 0.34 -0.00
60987 0.33 0.01
60988 0.36 -0.01
60989 0.41 -0.04
60990 0.46 -0.06
60991 0.45 -0.05
60992 0.42 -0.02
60993 0.39 0.01
60994 0.33 0.07
60995 0.29 0.12
60996 0.30 0.14
60997 0.38 0.10
60998 0.59 -0.02
60999 0.62 -0.11
61000 0.51 -0.14
61001 0.41 -0.14
61002 0.35 -0.10
61003 0.32 -0.06
61004 0.33 -0.03
61005 0.38 -0.07
61006 0.47 -0.10
61007 0.57 -0.11
61008 0.57 -0.10
61009 0.48 -0.08
61010 0.35 -0.06
)--"; )--";

619
mcc/mcc_coord.h Normal file
View File

@@ -0,0 +1,619 @@
#pragma once
#include "mcc_angle.h"
#include "mcc_ccte_erfa_new.h"
#include "mcc_defaults.h"
#include "mcc_generics.h"
#include <erfa.h>
namespace mcc
{
template <mcc_angle_c CO_LON_T, mcc_angle_c CO_LAT_T>
class MccCoordPair
{
public:
typedef CO_LON_T x_t;
typedef CO_LAT_T y_t;
static constexpr MccCoordPairKind pairKind =
!(std::derived_from<CO_LON_T, MccAngle> ||
std::derived_from<CO_LAT_T, MccAngle>) // unknown type (possibly just double or float)
? MccCoordPairKind::COORDS_KIND_GENERIC
: (std::same_as<CO_LON_T, MccAngle> || std::same_as<CO_LAT_T, MccAngle>) // one of the types is MccAngle
? MccCoordPairKind::COORDS_KIND_GENERIC
// ICRS RA and DEC
: (std::same_as<CO_LON_T, MccAngleRA_ICRS> && std::same_as<CO_LAT_T, MccAngleDEC_ICRS>)
? MccCoordPairKind::COORDS_KIND_RADEC_ICRS
// apparent RA and DEC
: (std::same_as<CO_LON_T, MccAngleRA_APP> && std::same_as<CO_LAT_T, MccAngleDEC_APP>)
? MccCoordPairKind::COORDS_KIND_RADEC_APP
// observed RA and DEC
: (std::same_as<CO_LON_T, MccAngleRA_OBS> && std::same_as<CO_LAT_T, MccAngleDEC_OBS>)
? MccCoordPairKind::COORDS_KIND_RADEC_OBS
// apparent HA and DEC
: (std::same_as<CO_LON_T, MccAngleHA_APP> && std::same_as<CO_LAT_T, MccAngleDEC_APP>)
? MccCoordPairKind::COORDS_KIND_HADEC_APP
// observed HA and DEC
: (std::same_as<CO_LON_T, MccAngleHA_OBS> && std::same_as<CO_LAT_T, MccAngleDEC_OBS>)
? MccCoordPairKind::COORDS_KIND_HADEC_OBS
// apparent AZ and ZD
: (std::same_as<CO_LON_T, MccAngleAZ> && std::same_as<CO_LAT_T, MccAngleZD>)
? MccCoordPairKind::COORDS_KIND_AZZD
// apparent AZ and ALT
: (std::same_as<CO_LON_T, MccAngleAZ> && std::same_as<CO_LAT_T, MccAngleALT>)
? MccCoordPairKind::COORDS_KIND_AZALT
// general purpose X and Y
: (std::same_as<CO_LON_T, MccAngleX> && std::same_as<CO_LAT_T, MccAngleY>)
? MccCoordPairKind::COORDS_KIND_XY
// geographical longitude and latitude
: (std::same_as<CO_LON_T, MccAngleLON> && std::same_as<CO_LAT_T, MccAngleLAT>)
? MccCoordPairKind::COORDS_KIND_LONLAT
: MccCoordPairKind::COORDS_KIND_UNKNOWN;
template <mcc_coord_epoch_c EpT = MccCelestialCoordEpoch>
MccCoordPair(CO_LON_T const& x, CO_LAT_T const& y, EpT const& epoch = EpT::now()) : _x(x), _y(y), _epoch(epoch)
{
}
MccCoordPair(const MccCoordPair&) = default;
MccCoordPair(MccCoordPair&&) = default;
MccCoordPair& operator=(const MccCoordPair&) = default;
MccCoordPair& operator=(MccCoordPair&&) = default;
virtual ~MccCoordPair() = default;
CO_LON_T x() const
{
return _x;
}
CO_LAT_T y() const
{
return _y;
}
MccCelestialCoordEpoch epoch() const
{
return _epoch;
}
double MJD() const
{
return _epoch.MJD();
}
// for something like:
// auto [ra, dec, epoch] = coord_pair;
operator std::tuple<CO_LON_T, CO_LAT_T, MccCelestialCoordEpoch>() const
{
return {_x, _y, _epoch};
}
void setX(const CO_LON_T& x)
{
_x = x;
}
void setY(const CO_LAT_T& y)
{
_y = y;
}
void setEpoch(mcc_coord_epoch_c auto const& ep)
{
_epoch = ep;
}
protected:
CO_LON_T _x;
CO_LAT_T _y;
MccCelestialCoordEpoch _epoch;
};
template <typename T>
concept mcc_coord_pair_c = requires {
requires mcc_angle_c<typename T::x_t>;
requires mcc_angle_c<typename T::y_t>;
requires std::derived_from<T, MccCoordPair<typename T::x_t, typename T::y_t>>;
};
/* predefined coordinate pairs */
template <mcc_angle_c CO_LON_T, mcc_angle_c CO_LAT_T>
requires(std::derived_from<CO_LON_T, MccAngle> && std::derived_from<CO_LAT_T, MccAngle>)
class MccNamedCoordPair : public MccCoordPair<CO_LON_T, CO_LAT_T>
{
public:
MccNamedCoordPair() : MccCoordPair<CO_LON_T, CO_LAT_T>(CO_LON_T{0.0}, CO_LAT_T{0.0}, MccCelestialCoordEpoch::now())
{
}
template <typename CxT, typename CyT, mcc_coord_epoch_c EpT = MccCelestialCoordEpoch>
requires(std::is_arithmetic_v<CxT> && std::is_arithmetic_v<CyT>)
MccNamedCoordPair(CxT const& x, CyT const& y, EpT const& epoch = EpT::now())
: MccCoordPair<CO_LON_T, CO_LAT_T>(CO_LON_T{(double)x}, CO_LAT_T{(double)y}, epoch)
{
}
template <mcc_coord_epoch_c EpT = MccCelestialCoordEpoch>
MccNamedCoordPair(MccAngle const& x, MccAngle const& y, EpT const& epoch = EpT::now())
: MccCoordPair<CO_LON_T, CO_LAT_T>(CO_LON_T{(double)x}, CO_LAT_T{(double)y}, epoch)
{
}
MccNamedCoordPair(const MccNamedCoordPair&) = default;
MccNamedCoordPair(MccNamedCoordPair&&) = default;
MccNamedCoordPair& operator=(const MccNamedCoordPair&) = default;
MccNamedCoordPair& operator=(MccNamedCoordPair&&) = default;
virtual ~MccNamedCoordPair() = default;
};
struct MccSkyRADEC_ICRS : MccNamedCoordPair<MccAngleRA_ICRS, MccAngleDEC_ICRS> {
template <typename CxT, typename CyT>
requires(std::is_arithmetic_v<CxT> && std::is_arithmetic_v<CyT>)
MccSkyRADEC_ICRS(CxT const& x, CyT const& y)
: MccNamedCoordPair<MccAngleRA_ICRS, MccAngleDEC_ICRS>(x, y, MccCelestialCoordEpoch{})
{
}
MccSkyRADEC_ICRS(MccAngle const& x, MccAngle const& y) : MccSkyRADEC_ICRS((double)x, (double)y) {}
// ignore epoch setting (it is always J2000.0)
void setEpoch(mcc_coord_epoch_c auto const&)
{
static_assert(false, "CANNOT SET EPOCH FOR ICRS-KIND COORDINATE PAIR!!!");
}
};
struct MccSkyRADEC_APP : MccNamedCoordPair<MccAngleRA_APP, MccAngleDEC_APP> {
using MccNamedCoordPair<MccAngleRA_APP, MccAngleDEC_APP>::MccNamedCoordPair;
};
typedef MccNamedCoordPair<MccAngleRA_OBS, MccAngleDEC_OBS> MccSkyRADEC_OBS;
// struct MccSkyRADEC_OBS : MccNamedCoordPair<MccAngleRA_OBS, MccAngleDEC_OBS> {
// using MccNamedCoordPair<MccAngleRA_OBS, MccAngleDEC_OBS>::MccNamedCoordPair;
// };
struct MccSkyHADEC_APP : MccNamedCoordPair<MccAngleHA_APP, MccAngleDEC_APP> {
using MccNamedCoordPair<MccAngleHA_APP, MccAngleDEC_APP>::MccNamedCoordPair;
};
struct MccSkyHADEC_OBS : MccNamedCoordPair<MccAngleHA_OBS, MccAngleDEC_OBS> {
using MccNamedCoordPair<MccAngleHA_OBS, MccAngleDEC_OBS>::MccNamedCoordPair;
};
struct MccSkyAZZD : MccNamedCoordPair<MccAngleAZ, MccAngleZD> {
using MccNamedCoordPair<MccAngleAZ, MccAngleZD>::MccNamedCoordPair;
};
struct MccSkyAZALT : MccNamedCoordPair<MccAngleAZ, MccAngleALT> {
using MccNamedCoordPair<MccAngleAZ, MccAngleALT>::MccNamedCoordPair;
};
struct MccGenXY : MccNamedCoordPair<MccAngleX, MccAngleY> {
using MccNamedCoordPair<MccAngleX, MccAngleY>::MccNamedCoordPair;
};
struct MccGeoLONLAT : MccNamedCoordPair<MccAngleLON, MccAngleLAT> {
using MccNamedCoordPair<MccAngleLON, MccAngleLAT>::MccNamedCoordPair;
};
struct mcc_skypoint_interface_t {
virtual ~mcc_skypoint_interface_t() = default;
// template <std::derived_from<mcc_skypoint_interface_t> SelfT, mcc_angle_c XT, mcc_angle_c YT>
// SelfT& from(this SelfT&& self, XT&& x, YT&& y)
// {
// return std::forward<SelfT>(self).from(std::forward<XT>(x), std::forward<YT>(y));
// }
template <std::derived_from<mcc_skypoint_interface_t> SelfT, mcc_coord_pair_c PT>
auto from(this SelfT&& self, PT&& cpair)
{
return std::forward<SelfT>(self).from(std::forward<PT>(cpair));
}
template <std::derived_from<mcc_skypoint_interface_t> SelfT, mcc_coord_pair_c PT>
auto operator=(this SelfT&& self, PT&& cpair)
{
return std::forward<SelfT>(self).operator=(std::forward<PT>(cpair));
}
template <std::derived_from<mcc_skypoint_interface_t> SelfT, mcc_coord_pair_c PT, mcc_coord_pair_c... PTs>
auto to(this SelfT&& self, PT& cpair, PTs&... cpairs)
{
return std::forward<SelfT>(self).to(cpair, cpairs...);
}
template <std::derived_from<mcc_skypoint_interface_t> SelfT, mcc_coord_pair_c PT>
operator PT(this SelfT&& self)
{
return std::forward<SelfT>(self).operator PT();
}
};
template <typename T>
concept mcc_skypoint_c = std::derived_from<T, mcc_skypoint_interface_t> && requires(T t) {
typename T::meteo_t;
{ T::meteo(std::declval<typename T::meteo_t const&>()) };
};
/* MCC-LIBRARY DEFAULT GENERIC SKY POINT CLASS IMPLEMENTATION */
template <typename CCTE_T>
class MccGenericSkyPoint : public mcc_skypoint_interface_t
{
public:
typedef CCTE_T ccte_t;
static constexpr double MJD0 = 2400000.5;
inline static CCTE_T cctEngine{}; // celestial coordinates transformation engine
MccGenericSkyPoint() {}
template <mcc_coord_pair_c PT>
MccGenericSkyPoint(const PT& coord_pair) : MccGenericSkyPoint()
{
auto self = from(coord_pair);
}
MccGenericSkyPoint(const MccGenericSkyPoint&) = default;
MccGenericSkyPoint(MccGenericSkyPoint&&) = default;
MccGenericSkyPoint& operator=(const MccGenericSkyPoint&) = default;
MccGenericSkyPoint& operator=(MccGenericSkyPoint&&) = default;
virtual ~MccGenericSkyPoint() = default;
MccCelestialCoordEpoch epoch() const
{
return _epoch;
}
template <mcc_coord_pair_c PT>
MccGenericSkyPoint& from(const PT& coord_pair)
{
_x = coord_pair.x();
_y = coord_pair.y();
_pairKind = coord_pair.pairKind;
if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS) {
_epoch = MccCelestialCoordEpoch(); // J2000.0
} else {
_epoch.fromMJD(coord_pair.MJD());
}
return *this;
}
MccGenericSkyPoint& operator=(mcc_coord_pair_c auto const& coord_pair)
{
return from(coord_pair);
}
template <mcc_coord_pair_c PT, mcc_coord_pair_c... PTs>
auto to(PT& cpair, PTs&... cpairs) const
{
toHelper(cpair);
if constexpr (sizeof...(PTs)) {
to(cpairs...);
}
}
template <mcc_coord_pair_c PT>
operator PT()
{
PT res;
to(res);
return res;
}
protected:
double _x{0.0}, _y{0.0};
MccCoordPairKind _pairKind{MccCoordPairKind::COORDS_KIND_RADEC_ICRS};
MccCelestialCoordEpoch _epoch{}; // J2000.0
template <mcc_coord_pair_c PT>
auto toHelper(PT& cpair) const
{
static constexpr double half_pi = std::numbers::pi / 2.0;
// HA, DEC to AZ, ALT (AZ from the South through the West)
auto hadec2azalt = [](double ha, double dec, double phi, double& az, double& alt) {
const auto cos_phi = std::cos(phi), sin_phi = std::sin(phi);
const auto cos_dec = std::cos(dec), sin_dec = std::sin(dec);
const auto cos_ha = std::cos(ha), sin_ha = std::sin(ha);
auto x = sin_phi * cos_dec * cos_ha - cos_phi * sin_dec;
auto y = -cos_dec * sin_ha;
auto z = cos_phi * cos_dec * cos_ha + sin_phi * sin_dec;
auto xx = x * x, yy = y * y;
decltype(x) r;
if (xx < yy) {
r = yy * sqrt(1.0 + xx / yy);
} else {
r = xx * sqrt(1.0 + yy / xx);
}
az = utils::isEqual(r, 0.0) ? 0.0 : std::atan2(y, x);
if (az < 0.0) {
az += std::numbers::pi * 2.0; // to range of [0, 2*PI]
}
alt = std::atan2(z, r);
};
// AZ, ALT to HA, DEC (AZ from the South through the West)
auto azalt2hadec = [](double az, double alt, double phi, double& ha, double& dec) {
const auto cos_phi = std::cos(phi), sin_phi = std::sin(phi);
const auto cos_az = std::cos(az), sin_az = std::sin(az);
const auto cos_alt = std::cos(alt), sin_alt = std::sin(alt);
auto x = sin_phi * cos_alt * cos_az + cos_phi * sin_alt;
auto y = cos_alt * sin_az;
auto z = -cos_phi * cos_alt * cos_az + sin_phi * sin_alt;
auto xx = x * x, yy = y * y;
decltype(x) r;
if (xx < yy) {
r = yy * sqrt(1.0 + xx / yy);
} else {
r = xx * sqrt(1.0 + yy / xx);
}
ha = utils::isEqual(r, 0.0) ? 0.0 : std::atan2(y, x);
dec = std::atan2(z, r);
};
typename CCTE_T::error_t ccte_err;
double phi = cctEngine.getStateERFA().lat;
double ra_icrs, dec_icrs, ra, dec, ha, az, zd, alt, lst, eo;
static_assert(PT::pairKind != MccCoordPairKind::COORDS_KIND_GENERIC, "UNSUPPORTED SKY POINT TRANSFORMATION!");
static_assert(PT::pairKind != MccCoordPairKind::COORDS_KIND_UNKNOWN, "UNSUPPORTED SKY POINT TRANSFORMATION!");
if (_pairKind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS &&
PT::pairKind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS) { // from ICRS to ICRS - just copy and exit
cpair = PT(typename PT::x_t(_x), typename PT::y_t(_y));
return;
}
// just copy coordinates and exit
if (_pairKind == PT::pairKind && utils::isEqual(_epoch.MJD(), cpair.MJD())) {
// cpair = PT(typename PT::x_t(_x), typename PT::y_t(_y), _epoch);
cpair.setX(_x);
cpair.setY(_y);
return;
}
// if epochs are not the same then
// 1) convert stored coordinates to ICRS ones
// 2) convert from the computed ICRS coordinates to required ones
MccCoordPairKind pkind = _pairKind;
if (!utils::isEqual(_epoch.MJD(), cpair.MJD())) { // convert stored pair to ICRS one (ra_icrs, dec_icrs)
if (_pairKind != MccCoordPairKind::COORDS_KIND_RADEC_ICRS) {
pkind = MccCoordPairKind::COORDS_KIND_RADEC_ICRS;
if (mcc_is_obs_coordpair(_pairKind)) {
ccte_err = cctEngine.obsToICRS(_pairKind, _epoch, _x, _y, &ra_icrs, &dec_icrs);
} else if (mcc_is_app_coordpair(_pairKind)) {
ccte_err = cctEngine.appToICRS(_pairKind, _epoch, _x, _y, &ra_icrs, &dec_icrs);
} else { // unsupported transformation!!!
return;
}
if (ccte_err) {
return;
}
} else {
ra_icrs = _x;
dec_icrs = _y;
}
}
// here, from APP or OBS to ICRS and exit
if (pkind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS &&
PT::pairKind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS) {
cpair = PT(typename PT::x_t(ra_icrs), typename PT::y_t(dec_icrs));
return;
}
// here, the input coordinates and stored one are at the same epoch
ccte_err = cctEngine.equationOrigins(cpair.epoch(), &eo);
if (ccte_err) {
return;
}
ccte_err = cctEngine.apparentSideralTime(cpair.epoch(), &lst, true);
if (ccte_err) {
return;
}
if (pkind == MccCoordPairKind::COORDS_KIND_RADEC_APP || pkind == MccCoordPairKind::COORDS_KIND_RADEC_OBS) {
ra = _x;
dec = _y;
} else if (pkind == MccCoordPairKind::COORDS_KIND_HADEC_APP ||
pkind == MccCoordPairKind::COORDS_KIND_HADEC_OBS) {
ha = _x;
dec = _y;
} else if (pkind == MccCoordPairKind::COORDS_KIND_AZZD) {
az = _x;
zd = _y;
} else if (pkind == MccCoordPairKind::COORDS_KIND_AZALT) {
az = _x;
alt = _y;
}
// else if (pkind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS) {
// ra_icrs = _x;
// dec_icrs = _y;
// } else { // unsupported transformation!!!
// return;
// }
// coordinate transformation lambda (possibly recursive!!!)
auto comp_func = [&](this auto&& self, MccCoordPairKind cp_kind) -> void {
if (cp_kind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS) {
if constexpr (mccIsAppCoordPairKind<PT::pairKind>) {
ccte_err = cctEngine.icrsToApp(ra_icrs, dec_icrs, cpair.epoch(), &ra, &dec, &ha, &az, &zd);
} else if constexpr (mccIsObsCoordPairKind<PT::pairKind>) {
ccte_err = cctEngine.icrsToObs(ra_icrs, dec_icrs, cpair.epoch(), &ra, &dec, &ha, &az, &zd);
} else {
static_assert(true, "UNSUPPORTED SKY POINT TRANSFORMATION!");
}
if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_RADEC_APP ||
PT::pairKind == MccCoordPairKind::COORDS_KIND_RADEC_OBS) {
cpair.setX(ra);
cpair.setY(dec);
} else if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_HADEC_APP ||
PT::pairKind == MccCoordPairKind::COORDS_KIND_HADEC_OBS) {
cpair.setX(ha);
cpair.setY(dec);
} else if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_AZZD) {
cpair.setX(az);
cpair.setY(zd);
} else if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_AZALT) {
cpair.setX(az);
cpair.setY(half_pi - zd);
} else {
static_assert(true, "UNSUPPORTED SKY POINT TRANSFORMATION!");
}
} else if (cp_kind == MccCoordPairKind::COORDS_KIND_AZALT) {
if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_AZZD) {
zd = half_pi - alt;
cpair.setX(az);
cpair.setY(zd);
} else {
if constexpr (mccIsAppCoordPairKind<PT::pairKind>) {
// correct for refraction: alt -= dz_refr
double dZ;
ccte_err = cctEngine.refractionCorrection(half_pi - alt, &dZ);
alt -= dZ;
}
azalt2hadec(az, alt, phi, ha, dec);
cpair.setY(dec);
if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_RADEC_APP) {
ra = lst + eo - ha;
cpair.setX(ra);
} else if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_RADEC_OBS) {
ra = lst + eo - ha;
cpair.setX(ra);
} else if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_HADEC_APP) {
cpair.setX(ha);
} else if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_HADEC_OBS) {
cpair.setX(ha);
} else { // unsupported transformation!!!
return;
}
}
} else if (cp_kind == MccCoordPairKind::COORDS_KIND_AZZD) {
alt = half_pi - zd;
if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_AZALT) {
cpair.setX(az);
cpair.setY(alt);
} else {
self(MccCoordPairKind::COORDS_KIND_AZALT);
}
} else if (cp_kind == MccCoordPairKind::COORDS_KIND_HADEC_OBS) {
if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_RADEC_OBS) {
ra = lst + eo - ha;
cpair.setX(ra);
cpair.setY(dec);
} else {
hadec2azalt(ha, dec, phi, az, alt);
if constexpr (mccIsAppCoordPairKind<PT::pairKind>) { // RADEC_APP, HADEC_APP
self(MccCoordPairKind::COORDS_KIND_AZALT);
} else { // AZALT, AZZD
cpair.setX(az);
if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_AZZD) {
zd = half_pi - alt;
cpair.setY(zd);
} else {
cpair.setY(alt);
}
}
}
} else if (cp_kind == MccCoordPairKind::COORDS_KIND_HADEC_APP) {
if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_RADEC_APP) {
ra = lst + eo - ha;
cpair.setX(ra);
cpair.setY(dec);
} else {
hadec2azalt(ha, dec, phi, az, alt);
if constexpr (mccIsObsCoordPairKind<PT::pairKind>) { // RADEC_OBS, HADEC_OBS, AZALT, AZZD
// correct for refraction: alt += dz_refr
double dZ;
ccte_err = cctEngine.refractionReverseCorrection(half_pi - alt, &dZ);
alt += dZ;
self(MccCoordPairKind::COORDS_KIND_AZALT);
}
}
} else if (cp_kind == MccCoordPairKind::COORDS_KIND_RADEC_OBS) {
ha = lst + eo - ra;
if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_HADEC_OBS) {
cpair.setX(ha);
cpair.setY(dec);
} else {
self(MccCoordPairKind::COORDS_KIND_HADEC_OBS);
}
} else if (cp_kind == MccCoordPairKind::COORDS_KIND_RADEC_APP) {
ha = lst + eo - ra;
if constexpr (PT::pairKind == MccCoordPairKind::COORDS_KIND_HADEC_APP) {
cpair.setX(ha);
cpair.setY(dec);
} else {
self(MccCoordPairKind::COORDS_KIND_HADEC_APP);
}
}
};
comp_func(pkind); // ran transformation
}
};
/* MCC-LIBRARY DEFAULT SKY POINT CLASS WITH ERFA-LIBRARY BASED ENGINE */
typedef MccGenericSkyPoint<ccte::erfa::MccCCTE_ERFA> MccSkyPoint;
} // end namespace mcc

View File

@@ -98,6 +98,52 @@ public:
MccCelestialCoordEpoch() : _UTC(J2000_UTC), _MJD(J2000_MJD), _JEpoch(2000.0) {} MccCelestialCoordEpoch() : _UTC(J2000_UTC), _MJD(J2000_MJD), _JEpoch(2000.0) {}
MccCelestialCoordEpoch(const MccCelestialCoordEpoch&) = default;
MccCelestialCoordEpoch(MccCelestialCoordEpoch&&) = default;
MccCelestialCoordEpoch& operator=(const MccCelestialCoordEpoch&) = default;
MccCelestialCoordEpoch& operator=(MccCelestialCoordEpoch&&) = default;
MccCelestialCoordEpoch(mcc_coord_epoch_c auto&& other) : MccCelestialCoordEpoch()
{
fromTimePoint(std::forward<decltype(other)>(other).UTC());
}
MccCelestialCoordEpoch& operator=(mcc_coord_epoch_c auto&& other)
{
fromTimePoint(std::forward<decltype(other)>(other).UTC());
return *this;
}
MccCelestialCoordEpoch& operator=(traits::mcc_input_char_range auto&& str)
{
// ignore possible errors!!!
auto ok = fromCharRange(std::forward<decltype(str)>(str));
return *this;
}
template <typename ClockT, typename DurT>
MccCelestialCoordEpoch& operator=(std::chrono::time_point<ClockT, DurT>&& tp)
{
// ignore possible errors!!!
auto ok = fromTimePoint(std::forward<decltype(tp)>(tp));
return *this;
}
template <typename VT>
MccCelestialCoordEpoch& operator=(VT&& mjd)
requires std::is_arithmetic_v<VT>
{
// ignore possible errors!!!
auto ok = fromMJD(std::forward<decltype(mjd)>(mjd));
return *this;
}
template <traits::mcc_input_char_range IR> template <traits::mcc_input_char_range IR>
bool fromCharRange(IR&& str) bool fromCharRange(IR&& str)
{ {
@@ -441,23 +487,12 @@ struct MccGenericEqtHrzCoords : MccGenericCelestialPoint<CoordT> {
using MccGenericCelestialPoint<CoordT>::time_point; using MccGenericCelestialPoint<CoordT>::time_point;
coord_t RA_APP{}, DEC_APP{}, HA{}, AZ{}, ZD{}, ALT{}; coord_t RA_ICRS{}, DEC_ICRS{}, RA_APP{}, DEC_APP{}, HA{}, AZ{}, ZD{}, ALT{};
}; };
typedef MccGenericEqtHrzCoords<MccCelestialPoint::coord_t> MccEqtHrzCoords; typedef MccGenericEqtHrzCoords<MccCelestialPoint::coord_t> MccEqtHrzCoords;
template <mcc_angle_c CoordT>
struct MccGenericPointingTarget : MccGenericEqtHrzCoords<CoordT> {
using typename MccGenericEqtHrzCoords<CoordT>::coord_t;
coord_t RA_ICRS{}, DEC_ICRS{};
};
typedef MccGenericPointingTarget<MccCelestialPoint::coord_t> MccPointingTarget;
template <mcc_angle_c CoordT> template <mcc_angle_c CoordT>
struct MccGenericPCMResult { struct MccGenericPCMResult {
@@ -477,7 +512,9 @@ struct MccGenericTelemetryData : MccGenericEqtHrzCoords<CoordT> {
MccJulianDay JD; MccJulianDay JD;
coord_t LST; // local apparent sideral time coord_t LST; // local apparent sideral time
MccGenericPointingTarget<coord_t> target{}; MccGenericCelestialPoint<coord_t> entered_target{};
MccGenericEqtHrzCoords<coord_t> target{};
coord_t speedX, speedY; coord_t speedX, speedY;
@@ -503,7 +540,6 @@ struct MccPositionControls : CCTE_T, HARDWARE_T, PCM_T {
static_assert(mcc_julday_c<MccJulianDay>, ""); static_assert(mcc_julday_c<MccJulianDay>, "");
static_assert(mcc_celestial_point_c<MccCelestialPoint>, ""); static_assert(mcc_celestial_point_c<MccCelestialPoint>, "");
static_assert(mcc_pointing_target_coord_c<MccGenericPointingTarget<double>>, "");
static_assert(mcc_telemetry_data_c<MccTelemetryData>, ""); static_assert(mcc_telemetry_data_c<MccTelemetryData>, "");
@@ -916,13 +952,20 @@ public:
template <mcc_eqt_hrz_coord_c T, traits::mcc_output_char_range OR> template <mcc_eqt_hrz_coord_c T, traits::mcc_output_char_range OR>
void operator()(const T& value, OR& bytes) void operator()(const T& value, OR& bytes)
{ {
// output format: RA, DEC, HA, AZ, ZD, ALT, X, Y, pair-kind, time-point // output format: RA_ICRS, DEC_ICRS, RA, DEC, HA, AZ, ZD, ALT, X, Y, pair-kind, time-point
// in the case of sexagesimal output X,Y coordinates will be interpretated // in the case of sexagesimal output X,Y coordinates will be interpretated
// according to value.pair_kind field // according to value.pair_kind field
if (_currentFormat == SerializedCoordFormat::CFMT_DEGREES) { if (_currentFormat == SerializedCoordFormat::CFMT_DEGREES) {
toDegrees(bytes, value.RA_APP, value.DEC_APP, value.HA, value.AZ, value.ZD, value.ALT, value.X, value.Y); toDegrees(bytes, value.RA_ICRS, value.DEC_ICRS, value.RA_APP, value.DEC_APP, value.HA, value.AZ, value.ZD,
value.ALT, value.X, value.Y);
} else if (_currentFormat == SerializedCoordFormat::CFMT_SGM) { } else if (_currentFormat == SerializedCoordFormat::CFMT_SGM) {
toSexagesimalHour(bytes, value.RA_ICRS);
std::format_to(std::back_inserter(bytes), "{}", _delimiter);
toSexagesimalDeg(bytes, value.DEC_ICRS);
std::format_to(std::back_inserter(bytes), "{}", _delimiter);
toSexagesimalHour(bytes, value.RA_APP); toSexagesimalHour(bytes, value.RA_APP);
std::format_to(std::back_inserter(bytes), "{}", _delimiter); std::format_to(std::back_inserter(bytes), "{}", _delimiter);
@@ -965,54 +1008,65 @@ public:
template <traits::mcc_input_char_range IR, mcc_eqt_hrz_coord_c T> template <traits::mcc_input_char_range IR, mcc_eqt_hrz_coord_c T>
std::error_code operator()(IR&& bytes, T& value) std::error_code operator()(IR&& bytes, T& value)
{ {
// valid format: RA, DEC, HA, AZ, ZD, ALT, X, Y, pair-kind, time-point // valid format: RA_ICRS, DEC_ICRS, RA, DEC, HA, AZ, ZD, ALT, X, Y, pair-kind, time-point
// in the case of sexagesimal input the X,Y coordinates will be interpretated // in the case of sexagesimal input the X,Y coordinates will be interpretated
// according to value.pair_kind field // according to value.pair_kind field
auto els = splitToElements(std::forward<IR>(bytes)); auto els = splitToElements(std::forward<IR>(bytes));
if (els.size() < 10) { if (els.size() < 12) {
// return std::make_error_code(std::errc::invalid_argument); // return std::make_error_code(std::errc::invalid_argument);
return MccCoordinateConvErrorCode::ERROR_ARG_LEN; return MccCoordinateConvErrorCode::ERROR_ARG_LEN;
} }
MccEqtHrzCoords pt; MccEqtHrzCoords pt;
pt.pair_kind = MccCoordStrToPairKind(els[8]); pt.pair_kind = MccCoordStrToPairKind(els[10]);
if (pt.pair_kind == MccCoordPairKind::COORDS_KIND_UNKNOWN) { if (pt.pair_kind == MccCoordPairKind::COORDS_KIND_UNKNOWN) {
// return std::make_error_code(std::errc::invalid_argument); // return std::make_error_code(std::errc::invalid_argument);
return MccCoordinateConvErrorCode::ERROR_INVALID_CPAIR; return MccCoordinateConvErrorCode::ERROR_INVALID_CPAIR;
} }
auto err = parseTimePoint(els[9], pt); auto err = parseTimePoint(els[11], pt);
if (err) { if (err) {
return err; return err;
} }
err = parseHourRepr(els[0], pt.RA_APP); size_t idx = 0;
err = parseHourRepr(els[idx++], pt.RA_ICRS);
if (err) { if (err) {
return err; return err;
} }
err = parseDegreeRepr(els[1], pt.DEC_APP); err = parseDegreeRepr(els[idx++], pt.DEC_ICRS);
if (err) { if (err) {
return err; return err;
} }
err = parseHourRepr(els[2], pt.HA); err = parseHourRepr(els[idx++], pt.RA_APP);
if (err) { if (err) {
return err; return err;
} }
err = parseDegreeRepr(els[4], pt.AZ); err = parseDegreeRepr(els[idx++], pt.DEC_APP);
if (err) { if (err) {
return err; return err;
} }
err = parseDegreeRepr(els[5], pt.ZD); err = parseHourRepr(els[idx++], pt.HA);
if (err) { if (err) {
return err; return err;
} }
err = parseDegreeRepr(els[6], pt.ALT); err = parseDegreeRepr(els[idx++], pt.AZ);
if (err) {
return err;
}
err = parseDegreeRepr(els[idx++], pt.ZD);
if (err) {
return err;
}
err = parseDegreeRepr(els[idx++], pt.ALT);
if (err) { if (err) {
return err; return err;
} }
@@ -1021,16 +1075,16 @@ public:
case MccCoordPairKind::COORDS_KIND_RADEC_ICRS: case MccCoordPairKind::COORDS_KIND_RADEC_ICRS:
case MccCoordPairKind::COORDS_KIND_RADEC_APP: case MccCoordPairKind::COORDS_KIND_RADEC_APP:
case MccCoordPairKind::COORDS_KIND_HADEC_APP: case MccCoordPairKind::COORDS_KIND_HADEC_APP:
err = parseHourRepr(els[7], pt.X); err = parseHourRepr(els[idx++], pt.X);
break; break;
default: default:
err = parseDegreeRepr(els[7], pt.X); err = parseDegreeRepr(els[idx++], pt.X);
} }
if (err) { if (err) {
return err; return err;
} }
err = parseDegreeRepr(els[8], pt.Y); err = parseDegreeRepr(els[idx++], pt.Y);
if (err) { if (err) {
return err; return err;
} }
@@ -1043,74 +1097,74 @@ public:
class MccPointingTargetSerializer : public MccCoordinateSerializer // class MccPointingTargetSerializer : public MccCoordinateSerializer
{ // {
public: // public:
template <mcc_pointing_target_coord_c T, traits::mcc_output_char_range OR> // template <mcc_pointing_target_coord_c T, traits::mcc_output_char_range OR>
void operator()(const T& value, OR& bytes) // void operator()(const T& value, OR& bytes)
{ // {
static MccEqtHrzCoordsSerializer eqhrz_ser; // static MccEqtHrzCoordsSerializer eqhrz_ser;
// output format: RA_ICRS, DEC_ICRS, RA_APP, DEC_APP, HA, AZ, ZD, ALT, X, Y, pair-kind, time-point // // output format: RA_ICRS, DEC_ICRS, RA_APP, DEC_APP, HA, AZ, ZD, ALT, X, Y, pair-kind, time-point
// in the case of sexagesimal output X,Y coordinates will be interpretated // // in the case of sexagesimal output X,Y coordinates will be interpretated
// according to value.pair_kind field // // according to value.pair_kind field
if (_currentFormat == SerializedCoordFormat::CFMT_DEGREES) { // if (_currentFormat == SerializedCoordFormat::CFMT_DEGREES) {
toDegrees(bytes, value.RA_ICRS, value.DEC_ICRS); // toDegrees(bytes, value.RA_ICRS, value.DEC_ICRS);
} else if (_currentFormat == SerializedCoordFormat::CFMT_SGM) { // } else if (_currentFormat == SerializedCoordFormat::CFMT_SGM) {
toSexagesimalHour(bytes, value.RA_ICRS); // toSexagesimalHour(bytes, value.RA_ICRS);
std::format_to(std::back_inserter(bytes), "{}", _delimiter); // std::format_to(std::back_inserter(bytes), "{}", _delimiter);
toSexagesimalDeg(bytes, value.DEC_ICRS); // toSexagesimalDeg(bytes, value.DEC_ICRS);
} // }
std::format_to(std::back_inserter(bytes), "{}", _delimiter); // std::format_to(std::back_inserter(bytes), "{}", _delimiter);
eqhrz_ser.setFormat(_currentFormat); // eqhrz_ser.setFormat(_currentFormat);
eqhrz_ser.setPrecision(_currentPrec); // eqhrz_ser.setPrecision(_currentPrec);
eqhrz_ser(value, bytes); // eqhrz_ser(value, bytes);
// MccEqtHrzCoordsSerializer{}(value, bytes); // // MccEqtHrzCoordsSerializer{}(value, bytes);
} // }
}; // };
class MccPointingTargetDeserializer : public MccCoordinateDeserializer // class MccPointingTargetDeserializer : public MccCoordinateDeserializer
{ // {
public: // public:
template <traits::mcc_input_char_range IR, mcc_pointing_target_coord_c T> // template <traits::mcc_input_char_range IR, mcc_pointing_target_coord_c T>
std::error_code operator()(IR&& bytes, T& value) // std::error_code operator()(IR&& bytes, T& value)
{ // {
// valid format: RA_ICRS, DEC_ICRS, RA_APP, DEC_APP, HA, AZ, ZD, ALT, X, Y, pair-kind, time-point // // valid format: RA_ICRS, DEC_ICRS, RA_APP, DEC_APP, HA, AZ, ZD, ALT, X, Y, pair-kind, time-point
// in the case of sexagesimal input the X,Y coordinates will be interpretated // // in the case of sexagesimal input the X,Y coordinates will be interpretated
// according to value.pair_kind field // // according to value.pair_kind field
auto els = splitToElements(std::forward<IR>(bytes)); // auto els = splitToElements(std::forward<IR>(bytes));
if (els.size() < 12) { // if (els.size() < 12) {
// return std::make_error_code(std::errc::invalid_argument); // // return std::make_error_code(std::errc::invalid_argument);
return MccCoordinateConvErrorCode::ERROR_ARG_LEN; // return MccCoordinateConvErrorCode::ERROR_ARG_LEN;
} // }
MccPointingTarget pt; // MccPointingTarget pt;
auto err = parseHourRepr(els[0], pt.RA_ICRS); // auto err = parseHourRepr(els[0], pt.RA_ICRS);
if (err) { // if (err) {
return err; // return err;
} // }
err = parseDegreeRepr(els[1], pt.DEC_ICRS); // err = parseDegreeRepr(els[1], pt.DEC_ICRS);
if (err) { // if (err) {
return err; // return err;
} // }
err = MccEqtHrzCoordsDeserializer{}(std::string_view{els[2].begin(), els[11].end()}, pt); // err = MccEqtHrzCoordsDeserializer{}(std::string_view{els[2].begin(), els[11].end()}, pt);
if (err) { // if (err) {
return err; // return err;
} // }
mcc_copy_pointing_target_coord(pt, &value); // mcc_copy_pointing_target_coord(pt, &value);
return {}; // return {};
} // }
}; // };
class MccTelemetryDataSerializer : public MccCoordinateSerializer class MccTelemetryDataSerializer : public MccCoordinateSerializer
@@ -1120,13 +1174,13 @@ public:
void operator()(const T& value, OR& bytes) void operator()(const T& value, OR& bytes)
{ {
static MccEqtHrzCoordsSerializer eqhrz_ser; static MccEqtHrzCoordsSerializer eqhrz_ser;
static MccPointingTargetSerializer pt_ser; // static MccPointingTargetSerializer pt_ser;
// output format: <mount data>, speedX, speedY, pcmX, pcmY, refCorr (in arcsecs), <target data> // output format: <mount data>, speedX, speedY, pcmX, pcmY, refCorr (in arcsecs), <target data>
// RA-APP_mnt, DEC-APP_mnt, HA_mnt, AZ_mnt, ZD_mnt, ALT_mnt, X_mnt, Y_mnt, COO-PAIR_mnt, TIME-POINT_mnt, // RA-ICRS_mnt, DEC-ICRS_mnt, RA-APP_mnt, DEC-APP_mnt, HA_mnt, AZ_mnt, ZD_mnt, ALT_mnt, X_mnt, Y_mnt,
// LST, EO, SPEED_X_mnt, SPEED_Y_mnt, PCM_X, PCM_Y, REFCORR, // COO-PAIR_mnt, TIME-POINT_mnt, LST, EO, SPEED_X_mnt, SPEED_Y_mnt, PCM_X, PCM_Y, REFCORR, RA-ICRS_tag,
// RA-ICRS_tag, DEC-ICRS_tag, RA-APP_tag, DEC-APP_tag, HA_tag, AZ_tag, ZD_tag, ALT_tag, X_tag, Y_tag, // DEC-ICRS_tag, RA-APP_tag, DEC-APP_tag, HA_tag, AZ_tag, ZD_tag, ALT_tag, X_tag, Y_tag, COO-PAIR_tag,
// COO-PAIR_tag, TIME-POINT_tag // TIME-POINT_tag
eqhrz_ser.setFormat(_currentFormat); eqhrz_ser.setFormat(_currentFormat);
eqhrz_ser.setPrecision(_currentPrec); eqhrz_ser.setPrecision(_currentPrec);
@@ -1145,9 +1199,11 @@ public:
toSexagesimalDeg(bytes, value.speedX, value.speedY, value.pcmX, value.pcmY, value.refCorr); toSexagesimalDeg(bytes, value.speedX, value.speedY, value.pcmX, value.pcmY, value.refCorr);
std::format_to(std::back_inserter(bytes), "{}", _delimiter); std::format_to(std::back_inserter(bytes), "{}", _delimiter);
pt_ser.setFormat(_currentFormat); // pt_ser.setFormat(_currentFormat);
pt_ser.setPrecision(_currentPrec); // pt_ser.setPrecision(_currentPrec);
pt_ser(value.target, bytes); // pt_ser(value.target, bytes);
eqhrz_ser(value.target, bytes);
} }
}; };
@@ -1163,18 +1219,18 @@ public:
// valid format: <mount data>, speedX, speedY, pcmX, pcmY, refCorr, <target data> // valid format: <mount data>, speedX, speedY, pcmX, pcmY, refCorr, <target data>
auto els = splitToElements(std::forward<IR>(bytes)); auto els = splitToElements(std::forward<IR>(bytes));
if (els.size() < 29) { if (els.size() < 31) {
// return std::make_error_code(std::errc::invalid_argument); // return std::make_error_code(std::errc::invalid_argument);
return MccCoordinateConvErrorCode::ERROR_ARG_LEN; return MccCoordinateConvErrorCode::ERROR_ARG_LEN;
} }
MccTelemetryData tdata; MccTelemetryData tdata;
auto err = MccEqtHrzCoordsDeserializer{}(std::string_view{els[0].begin(), els[9].end()}, tdata); auto err = MccEqtHrzCoordsDeserializer{}(std::string_view{els[0].begin(), els[11].end()}, tdata);
if (err) { if (err) {
return err; return err;
} }
size_t idx = 10; size_t idx = 12;
err = parseHourRepr(els[idx++], tdata.LST); err = parseHourRepr(els[idx++], tdata.LST);
if (err) { if (err) {
@@ -1211,7 +1267,8 @@ public:
return err; return err;
} }
err = MccPointingTargetDeserializer{}(std::string_view{els[idx].begin(), els.back().end()}, tdata.target); // err = MccPointingTargetDeserializer{}(std::string_view{els[idx].begin(), els.back().end()}, tdata.target);
err = MccEqtHrzCoordsDeserializer{}(std::string_view{els[idx].begin(), els.back().end()}, tdata.target);
if (err) { if (err) {
return err; return err;
} }

View File

@@ -20,7 +20,11 @@ enum class MccGenericMountErrorCode : int {
ERROR_HW_GETSTATE, ERROR_HW_GETSTATE,
ERROR_SET_TARGET, ERROR_SET_TARGET,
ERROR_MOUNT_SLEW, ERROR_MOUNT_SLEW,
ERROR_MOUNT_TRACK ERROR_MOUNT_TRACK,
ERROR_GET_TELEMETRY,
ERROR_UNSUPPORTED_TARGET_COORDPAIR,
ERROR_PZONE_COMP,
ERROR_TARGET_IN_ZONE
}; };
enum class MccGenericFsmMountErrorCode : int { ERROR_OK, ERROR_INVALID_OPERATION, ERROR_UNKNOWN_EVENT }; enum class MccGenericFsmMountErrorCode : int { ERROR_OK, ERROR_INVALID_OPERATION, ERROR_UNKNOWN_EVENT };
@@ -70,6 +74,20 @@ struct MccGenericMountCategory : public std::error_category {
return "an error occured while stopping mount"; return "an error occured while stopping mount";
case MccGenericMountErrorCode::ERROR_HW_GETSTATE: case MccGenericMountErrorCode::ERROR_HW_GETSTATE:
return "cannot get state of hardware"; return "cannot get state of hardware";
case MccGenericMountErrorCode::ERROR_SET_TARGET:
return "cannot set target coordinates";
case MccGenericMountErrorCode::ERROR_MOUNT_SLEW:
return "slewing error";
case MccGenericMountErrorCode::ERROR_MOUNT_TRACK:
return "tracking error";
case MccGenericMountErrorCode::ERROR_GET_TELEMETRY:
return "cannot get telemetry data";
case MccGenericMountErrorCode::ERROR_UNSUPPORTED_TARGET_COORDPAIR:
return "unsupported coordinate pair of target";
case MccGenericMountErrorCode::ERROR_PZONE_COMP:
return "an error occured while computing prohibited zone";
case MccGenericMountErrorCode::ERROR_TARGET_IN_ZONE:
return "target coordinates are in prohibitted zone";
default: default:
return "UNKNOWN"; return "UNKNOWN";
} }
@@ -186,7 +204,8 @@ public:
PZoneContT(std::make_from_tuple<PZoneContT>(std::move(pzcont_ctor_args))), PZoneContT(std::make_from_tuple<PZoneContT>(std::move(pzcont_ctor_args))),
SlewModelT(std::make_from_tuple<SlewModelT>(std::move(smodel_ctor_args))), SlewModelT(std::make_from_tuple<SlewModelT>(std::move(smodel_ctor_args))),
TrackModelT(std::make_from_tuple<TrackModelT>(std::move(tmodel_ctor_args))), TrackModelT(std::make_from_tuple<TrackModelT>(std::move(tmodel_ctor_args))),
_mountStatus(new std::atomic<mount_status_t>{}) _mountStatus(new std::atomic<mount_status_t>{}),
_lastMountError(new std::atomic<error_t>{MccGenericMountErrorCode::ERROR_OK})
{ {
*_mountStatus = mount_status_t::IDLE; *_mountStatus = mount_status_t::IDLE;
} }
@@ -200,12 +219,18 @@ public:
virtual ~MccGenericMount() virtual ~MccGenericMount()
{ {
stopMount(); stopMount();
if (_slewingFuture.valid()) {
_slewingFuture.wait_for(std::chrono::seconds(3));
}
}; };
error_t stopMount() error_t stopMount()
{ {
logInfo("Stop any movements ..."); logInfo("Stop any movements ...");
*_lastMountError = MccGenericMountErrorCode::ERROR_OK;
this->stopTracking(); this->stopTracking();
this->stopSlewing(); this->stopSlewing();
@@ -213,34 +238,36 @@ public:
if (hw_err) { if (hw_err) {
*_mountStatus = mount_status_t::ERROR; *_mountStatus = mount_status_t::ERROR;
return mcc_deduce_error_code(hw_err, MccGenericMountErrorCode::ERROR_HW_STOP); *_lastMountError = mcc_deduce_error_code(hw_err, MccGenericMountErrorCode::ERROR_HW_STOP);
} else {
logInfo("Stop command was sent");
*_mountStatus = mount_status_t::STOPPED;
} }
logInfo("Stop command was sent"); return *_lastMountError;
*_mountStatus = mount_status_t::STOPPED;
return MccGenericMountErrorCode::ERROR_OK;
} }
error_t initMount() error_t initMount()
{ {
logInfo("Start generic mount initialization ..."); logInfo("Start generic mount initialization ...");
*_lastMountError = MccGenericMountErrorCode::ERROR_OK;
*_mountStatus = mount_status_t::INITIALIZATION; *_mountStatus = mount_status_t::INITIALIZATION;
auto hw_err = this->hardwareInit(); auto hw_err = this->hardwareInit();
if (hw_err) { if (hw_err) {
*_mountStatus = mount_status_t::ERROR; *_mountStatus = mount_status_t::ERROR;
return mcc_deduce_error_code(hw_err, MccGenericMountErrorCode::ERROR_HW_STOP); *_lastMountError = mcc_deduce_error_code(hw_err, MccGenericMountErrorCode::ERROR_HW_INIT);
} else {
logInfo("Generic mount initialization was performed");
*_mountStatus = mount_status_t::IDLE;
} }
logInfo("Generic mount initialization was performed"); return *_lastMountError;
*_mountStatus = mount_status_t::IDLE;
return MccGenericMountErrorCode::ERROR_OK;
} }
// re-implements TelemetryT::setPointingTarget to hold target coordinates // re-implements TelemetryT::setPointingTarget to hold target coordinates
@@ -268,6 +295,47 @@ public:
// re-implements SlewModelT::slewToTarget to fetch input target coordinates from intermediate buffer // re-implements SlewModelT::slewToTarget to fetch input target coordinates from intermediate buffer
error_t slewToTarget(bool slew_and_stop = false) error_t slewToTarget(bool slew_and_stop = false)
{ {
*_lastMountError = MccGenericMountErrorCode::ERROR_OK;
// save current target
MccTelemetryData tdata;
auto sl_params = this->getSlewingParams();
auto t_err = this->telemetryData(&tdata);
if (t_err) {
return *_lastMountError = mcc_deduce_error_code(t_err, MccGenericMountErrorCode::ERROR_GET_TELEMETRY);
}
MccCelestialPoint curr_target{.pair_kind = tdata.target.pair_kind, .time_point = tdata.target.time_point};
if (curr_target.pair_kind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS) {
curr_target.X = tdata.target.RA_ICRS;
curr_target.Y = tdata.target.DEC_ICRS;
} else if (curr_target.pair_kind == MccCoordPairKind::COORDS_KIND_RADEC_APP) {
curr_target.X = tdata.target.RA_APP;
curr_target.Y = tdata.target.DEC_APP;
} else if (curr_target.pair_kind == MccCoordPairKind::COORDS_KIND_HADEC_APP) {
curr_target.X = tdata.target.HA;
curr_target.Y = tdata.target.DEC_APP;
} else if (curr_target.pair_kind == MccCoordPairKind::COORDS_KIND_AZZD) {
curr_target.X = tdata.target.AZ;
curr_target.Y = tdata.target.ZD;
} else if (curr_target.pair_kind == MccCoordPairKind::COORDS_KIND_AZALT) {
curr_target.X = tdata.target.AZ;
curr_target.Y = tdata.target.ALT;
} else if (curr_target.pair_kind == MccCoordPairKind::COORDS_KIND_XY) {
curr_target.X = tdata.target.X;
curr_target.Y = tdata.target.Y;
} else { // it should not be!
logError(
std::format("Unsupported coordinate pair kind ({}) was read from telemetry data! Is mount "
"initialized?! Cannot start slewing!",
(int)curr_target.pair_kind));
return *_lastMountError = MccGenericMountErrorCode::ERROR_UNSUPPORTED_TARGET_COORDPAIR;
}
// set new target coordinates and check it
_enteredTargetCoordiniates.time_point = std::chrono::system_clock::now(); _enteredTargetCoordiniates.time_point = std::chrono::system_clock::now();
auto err = TelemetryT::setPointingTarget(_enteredTargetCoordiniates); auto err = TelemetryT::setPointingTarget(_enteredTargetCoordiniates);
if (err) { if (err) {
@@ -276,23 +344,67 @@ public:
return mcc_deduce_error_code(err, MccGenericMountErrorCode::ERROR_SET_TARGET); return mcc_deduce_error_code(err, MccGenericMountErrorCode::ERROR_SET_TARGET);
} }
*_mountStatus = mount_status_t::SLEWING; t_err = this->waitForTelemetryData(&tdata, sl_params.telemetryTimeout);
if (t_err) {
error_t s_err = return mcc_deduce_error_code(t_err, MccGenericMountErrorCode::ERROR_GET_TELEMETRY);
mcc_deduce_error_code(SlewModelT::slewToTarget(slew_and_stop), MccGenericMountErrorCode::ERROR_MOUNT_SLEW);
if (s_err) {
*_mountStatus = mount_status_t::ERROR;
return s_err;
} }
if (slew_and_stop) { bool in_zone;
*_mountStatus = mount_status_t::IDLE; std::vector<bool> in_zone_vec;
} else { auto pz_err = this->inPZone(tdata.target, &in_zone, &in_zone_vec);
s_err = trackTarget(); if (pz_err) {
return *_lastMountError = mcc_deduce_error_code(pz_err, MccGenericMountErrorCode::ERROR_PZONE_COMP);
} }
return s_err; if (in_zone) {
size_t i = 0;
for (; i < in_zone_vec.size(); ++i) {
if (in_zone_vec[i]) {
break;
}
}
logError("target point is in prohibited zone (zone index: {})! Entered target coordinates:", i);
logError(std::format(
" RA-APP, DEC-APP, HA, LST: {}, {}, {}, {}", mcc::MccAngle{tdata.target.RA_APP}.sexagesimal(true),
mcc::MccAngle{tdata.target.DEC_APP}.sexagesimal(), mcc::MccAngle{tdata.target.HA}.sexagesimal(true),
mcc::MccAngle{tdata.LST}.sexagesimal(true)));
logError(std::format(" AZ, ZD, ALT: {}, {}, {}", mcc::MccAngle{tdata.target.AZ}.sexagesimal(),
mcc::MccAngle{tdata.target.ZD}.sexagesimal(),
mcc::MccAngle{tdata.target.ALT}.sexagesimal()));
logError(std::format(" hardware X, Y: {}, {}", mcc::MccAngle{tdata.target.X}.sexagesimal(),
mcc::MccAngle{tdata.target.Y}.sexagesimal()));
return *_lastMountError = mcc_deduce_error_code(pz_err, MccGenericMountErrorCode::ERROR_TARGET_IN_ZONE);
}
// start slewing (asunchronous operation)
_slewingFuture = std::async(std::launch::async,
[slew_and_stop, this]() {
*_mountStatus = mount_status_t::SLEWING;
auto err = SlewModelT::slewToTarget(slew_and_stop);
if (err) {
*_lastMountError =
mcc_deduce_error_code(err, MccGenericMountErrorCode::ERROR_MOUNT_SLEW);
*_mountStatus = mount_status_t::ERROR;
} else {
if (slew_and_stop) {
*_mountStatus = mount_status_t::IDLE;
} else {
*_lastMountError = trackTarget();
}
}
}
);
return MccGenericMountErrorCode::ERROR_OK;
} }
error_t trackTarget() error_t trackTarget()
@@ -321,8 +433,7 @@ public:
error_t stopTracking() error_t stopTracking()
{ {
// *_mountStatus = mount_status_t::IDLE; *_mountStatus = mount_status_t::IDLE;
_mountStatus->store(mount_status_t::IDLE);
TrackModelT::stopTracking(); TrackModelT::stopTracking();
@@ -335,10 +446,21 @@ public:
return *_mountStatus; return *_mountStatus;
} }
// returns last mount-action error code
error_t mountLastError() const
{
return *_lastMountError;
}
protected: protected:
MccCelestialPoint _enteredTargetCoordiniates{}; MccCelestialPoint _enteredTargetCoordiniates{};
std::unique_ptr<std::atomic<MccGenericMount::mount_status_t>> _mountStatus; std::unique_ptr<std::atomic<MccGenericMount::mount_status_t>> _mountStatus;
std::future<void> _slewingFuture{};
std::unique_ptr<std::atomic<error_t>> _lastMountError;
}; };

View File

@@ -142,7 +142,8 @@ static constexpr void mcc_tp2tp(const T1& from_tp1, T2& to_tp)
/* JULIAN DAY CLASS CONCEPT */ /* JULIAN DAY CLASS CONCEPT */
template <typename T> template <typename T>
concept mcc_julday_c = mcc_fp_type_like_c<T> && requires(const T v) { concept mcc_julday_c = mcc_fp_type_like_c<T> || requires(const T v) {
// concept mcc_julday_c = mcc_fp_type_like_c<T> && requires(const T v) {
// modified Julian Day // modified Julian Day
{ v.MJD() } -> std::convertible_to<double>; { v.MJD() } -> std::convertible_to<double>;
// comparison operators // comparison operators
@@ -281,6 +282,9 @@ static constexpr void mcc_copy_celestial_point(mcc_celestial_point_c auto const&
template <typename T> template <typename T>
concept mcc_eqt_hrz_coord_c = mcc_celestial_point_c<T> && requires(T t) { concept mcc_eqt_hrz_coord_c = mcc_celestial_point_c<T> && requires(T t) {
requires mcc_angle_c<decltype(t.RA_ICRS)>; // ICRS right ascention
requires mcc_angle_c<decltype(t.DEC_ICRS)>; // ICRS declination
requires mcc_angle_c<decltype(t.RA_APP)>; // right ascension requires mcc_angle_c<decltype(t.RA_APP)>; // right ascension
requires mcc_angle_c<decltype(t.DEC_APP)>; // declination requires mcc_angle_c<decltype(t.DEC_APP)>; // declination
requires mcc_angle_c<decltype(t.HA)>; // hour angle requires mcc_angle_c<decltype(t.HA)>; // hour angle
@@ -311,6 +315,9 @@ static constexpr void mcc_copy_eqt_hrz_coord(mcc_eqt_hrz_coord_c auto const& fro
to_pt->X = (double)from_pt.X; to_pt->X = (double)from_pt.X;
to_pt->Y = (double)from_pt.Y; to_pt->Y = (double)from_pt.Y;
to_pt->RA_ICRS = (double)from_pt.RA_ICRS;
to_pt->DEC_ICRS = (double)from_pt.DEC_ICRS;
to_pt->RA_APP = (double)from_pt.RA_APP; to_pt->RA_APP = (double)from_pt.RA_APP;
to_pt->DEC_APP = (double)from_pt.DEC_APP; to_pt->DEC_APP = (double)from_pt.DEC_APP;
@@ -511,7 +518,7 @@ concept mcc_hardware_c = requires(T t, const T t_const) {
// a type that defines at least HW_MOVE_ERROR, HW_MOVE_STOPPED, HW_MOVE_SLEWING, HW_MOVE_ADJUSTING, HW_MOVE_TRACKING // a type that defines at least HW_MOVE_ERROR, HW_MOVE_STOPPED, HW_MOVE_SLEWING, HW_MOVE_ADJUSTING, HW_MOVE_TRACKING
// and HW_MOVE_GUIDING compile-time constants. The main purpose of this type is a // and HW_MOVE_GUIDING compile-time constants. The main purpose of this type is a
// possible tunning of hardware hardwareSetState-related commands and detect stop-state // possible tunning of hardware hardwareSetState-related commands and detect stop end error states from hardware
// //
// e.g. an implementations can be as follows: // e.g. an implementations can be as follows:
// enum class hardware_moving_state_t: int {HW_MOVE_ERROR = -1, HW_MOVE_STOPPED = 0, HW_MOVE_SLEWING, // enum class hardware_moving_state_t: int {HW_MOVE_ERROR = -1, HW_MOVE_STOPPED = 0, HW_MOVE_SLEWING,
@@ -582,55 +589,17 @@ concept mcc_hardware_c = requires(T t, const T t_const) {
/* MOUNT TELEMETRY DATA CLASS CONCEPT */ /* MOUNT TELEMETRY DATA CLASS CONCEPT */
template <typename T>
concept mcc_pointing_target_coord_c = mcc_eqt_hrz_coord_c<T> && requires(T t) {
requires mcc_angle_c<decltype(t.RA_ICRS)>; // ICRS right ascention
requires mcc_angle_c<decltype(t.DEC_ICRS)>; // ICRS declination
};
static constexpr void mcc_copy_pointing_target_coord(mcc_pointing_target_coord_c auto const& from_pt,
mcc_pointing_target_coord_c auto* to_pt)
{
if (to_pt == nullptr) {
return;
}
using from_pt_t = std::remove_cvref_t<decltype(from_pt)>;
using to_pt_t = std::remove_cvref_t<decltype(*to_pt)>;
if constexpr (std::derived_from<to_pt_t, from_pt_t> && std::copyable<to_pt_t>) {
*to_pt = from_pt;
return;
}
to_pt->pair_kind = from_pt.pair_kind;
to_pt->time_point =
std::chrono::time_point_cast<typename decltype(to_pt->time_point)::duration>(from_pt.time_point);
to_pt->X = (double)from_pt.X;
to_pt->Y = (double)from_pt.Y;
to_pt->RA_ICRS = (double)from_pt.RA_ICRS;
to_pt->DEC_ICRS = (double)from_pt.DEC_ICRS;
to_pt->RA_APP = (double)from_pt.RA_APP;
to_pt->DEC_APP = (double)from_pt.DEC_APP;
to_pt->HA = (double)from_pt.HA;
to_pt->AZ = (double)from_pt.AZ;
to_pt->ZD = (double)from_pt.ZD;
to_pt->ALT = (double)from_pt.ALT;
}
template <typename T> template <typename T>
concept mcc_telemetry_data_c = mcc_eqt_hrz_coord_c<T> && std::default_initializable<T> && requires(T t) { concept mcc_telemetry_data_c = mcc_eqt_hrz_coord_c<T> && std::default_initializable<T> && requires(T t) {
// user entered target coordinates
requires mcc_celestial_point_c<decltype(t.entered_target)>;
// target target coordinates // target target coordinates
requires mcc_pointing_target_coord_c<decltype(t.target)>; requires mcc_eqt_hrz_coord_c<decltype(t.target)>;
// t.X and t.Y (from mcc_celestial_point_c) are encoder coordinates // t.X and t.Y (from mcc_celestial_point_c) are encoder coordinates
// t.* from mcc_eqt_hrz_coord_c are apparent mount pointing coordinates // t.* from mcc_eqt_hrz_coord_c are current mount coordinates
requires mcc_angle_c<decltype(t.speedX)>; // speed along X from hardware encoder requires mcc_angle_c<decltype(t.speedX)>; // speed along X from hardware encoder
requires mcc_angle_c<decltype(t.speedY)>; // speed along Y from hardware encoder requires mcc_angle_c<decltype(t.speedY)>; // speed along Y from hardware encoder
@@ -646,6 +615,31 @@ concept mcc_telemetry_data_c = mcc_eqt_hrz_coord_c<T> && std::default_initializa
}; };
// minimal library-wide telemetry data
template <typename T>
concept mcc_tlm_data_c = mcc_eqt_hrz_coord_c<T> && std::default_initializable<T> && requires(T t) {
// user entered target coordinates
requires mcc_celestial_point_c<decltype(t.entered_target)>;
// target target coordinates
requires mcc_eqt_hrz_coord_c<decltype(t.target)>;
// t.X and t.Y (from mcc_celestial_point_c) are encoder coordinates
// t.* from mcc_eqt_hrz_coord_c are current mount coordinates
requires mcc_PCM_result_c<decltype(t.pcm)>; // PCM correction
// atmospheric refraction correction for current zenithal distance
requires mcc_angle_c<decltype(t.refCorr)>; // for current .ZD
// equation of the origins (ERA-GST)
requires mcc_angle_c<decltype(t.EO)>;
// local sideral time
requires mcc_angle_c<decltype(t.LST)>;
};
static constexpr void mcc_copy_telemetry_data(mcc_telemetry_data_c auto const& from_pt, static constexpr void mcc_copy_telemetry_data(mcc_telemetry_data_c auto const& from_pt,
mcc_telemetry_data_c auto* to_pt) mcc_telemetry_data_c auto* to_pt)
{ {
@@ -670,6 +664,9 @@ static constexpr void mcc_copy_telemetry_data(mcc_telemetry_data_c auto const& f
to_pt->speedX = (double)from_pt.speedX; to_pt->speedX = (double)from_pt.speedX;
to_pt->speedY = (double)from_pt.speedY; to_pt->speedY = (double)from_pt.speedY;
to_pt->RA_ICRS = (double)from_pt.RA_ICRS;
to_pt->DEC_ICRS = (double)from_pt.DEC_ICRS;
to_pt->RA_APP = (double)from_pt.RA_APP; to_pt->RA_APP = (double)from_pt.RA_APP;
to_pt->DEC_APP = (double)from_pt.DEC_APP; to_pt->DEC_APP = (double)from_pt.DEC_APP;
@@ -683,7 +680,9 @@ static constexpr void mcc_copy_telemetry_data(mcc_telemetry_data_c auto const& f
to_pt->refCorr = (double)from_pt.refCorr; to_pt->refCorr = (double)from_pt.refCorr;
mcc_copy_pointing_target_coord(from_pt.target, &to_pt->target); mcc_copy_eqt_hrz_coord(from_pt.target, &to_pt->target);
mcc_copy_celestial_point(from_pt.entered_target, &to_pt->entered_target);
} }
@@ -759,6 +758,24 @@ template <typename T>
concept mcc_telemetry_c = std::derived_from<T, mcc_telemetry_interface_t<typename T::error_t>>; concept mcc_telemetry_c = std::derived_from<T, mcc_telemetry_interface_t<typename T::error_t>>;
// template <typename T, typename VT>
// concept mcc_retval_c = requires(T t) {
// //
// []<mcc_error_c ErrT>(std::expected<VT, ErrT>) {}(t);
// };
template <typename T>
concept mcc_telemetry_controls_c = requires(T t) {
requires mcc_telemetry_data_c<typename T::telemetry_data_t>;
{ t.telemetryData(std::declval<typename T::telemetry_data_t*>()) } -> mcc_error_c;
// { t.telemetryData() } -> mcc_retval_c<typename T::telemetry_data_t>;
{
t.setPointingTarget(std::declval<decltype(std::declval<typename T::telemetry_data_t>().entered_target)>())
} -> mcc_error_c;
};
/* PROHIBITED ZONE CLASS CONCEPT */ /* PROHIBITED ZONE CLASS CONCEPT */
@@ -975,6 +992,21 @@ concept mcc_tracking_model_c = requires(T t) {
// }; // };
// a concept of a class of mount moving control methods
template <typename T>
concept mcc_moving_controls_c = requires(T t) {
typename T::moving_params_t;
{ t.slewToTarget(std::declval<bool>()) } -> mcc_error_c;
{ t.trackTarget() } -> mcc_error_c;
{ t.setMovingParams(std::declval<typename T::moving_params_t>()) } -> mcc_error_c;
{ t.getMovingParams() } -> std::same_as<typename T::moving_params_t>;
{ t.stopMount() } -> mcc_error_c;
};
/* GENERIC MOUNT CLASS CONCEPT */ /* GENERIC MOUNT CLASS CONCEPT */
@@ -995,23 +1027,14 @@ concept mcc_all_controls_c = mcc_position_controls_c<T> && mcc_telemetry_c<T> &&
// 3) slewing and tracking, stop and init mount methods // 3) slewing and tracking, stop and init mount methods
template <typename T> template <typename T>
concept mcc_generic_mount_c = mcc_telemetry_c<T> && mcc_pzone_container_c<T> && requires(T t, const T t_const) { concept mcc_generic_mount_c = mcc_telemetry_c<T> && mcc_pzone_container_c<T> && requires(T t, const T t_const) {
// requires mcc_error_c<typename T::error_t>;
// slew mount to target (it is assumed that the target coordinates are determined in the telemetry data) // slew mount to target (it is assumed that the target coordinates are determined in the telemetry data)
{ t.slewToTarget(std::declval<bool>()) }; { t.slewToTarget(std::declval<bool>()) };
// { t.slewToTarget() } -> std::same_as<typename T::error_t>;
// track target, i.e., the mount moves with celestial speed // track target, i.e., the mount moves with celestial speed
{ t.trackTarget() }; { t.trackTarget() };
// { t.trackTarget() } -> std::same_as<typename T::error_t>;
// { t.startGuidingTarget() } -> std::same_as<typename T::error_t>;
// { t.stopGuidingTarget() } -> std::same_as<typename T::error_t>;
// stop any movement // stop any movement
{ t.stopMount() }; { t.stopMount() };
// { t.stopMount() } -> std::same_as<typename T::error_t>;
// init mount // init mount
{ t.initMount() }; { t.initMount() };

814
mcc/mcc_moving_controls.h Normal file
View File

@@ -0,0 +1,814 @@
#pragma once
#include <fstream>
#include "mcc_defaults.h"
#include "mcc_generics.h"
#include "mcc_moving_model_common.h"
namespace mcc
{
enum class MccSimpleMovingControlsErrorCode : int {
ERROR_OK,
ERROR_HW_GETSTATE,
ERROR_HW_SETSTATE,
ERROR_PCM_COMP,
ERROR_GET_TELEMETRY,
ERROR_DIST_TELEMETRY,
ERROR_PZONE_CONTAINER_COMP,
ERROR_TARGET_IN_PZONE,
ERROR_NEAR_PZONE,
ERROR_TIMEOUT,
ERROR_ALREADY_SLEW,
ERROR_ALREADY_STOPPED,
ERROR_STOPPED
};
} // namespace mcc
namespace std
{
template <>
class is_error_code_enum<mcc::MccSimpleMovingControlsErrorCode> : public true_type
{
};
} // namespace std
namespace mcc
{
// error category
struct MccSimpleMovingControlsCategory : public std::error_category {
MccSimpleMovingControlsCategory() : std::error_category() {}
const char* name() const noexcept
{
return "SIMPLE-SLEWING-MODEL";
}
std::string message(int ec) const
{
MccSimpleMovingControlsErrorCode err = static_cast<MccSimpleMovingControlsErrorCode>(ec);
switch (err) {
case MccSimpleMovingControlsErrorCode::ERROR_OK:
return "OK";
case MccSimpleMovingControlsErrorCode::ERROR_HW_GETSTATE:
return "cannot get hardware state";
case MccSimpleMovingControlsErrorCode::ERROR_HW_SETSTATE:
return "cannot set hardware state";
case MccSimpleMovingControlsErrorCode::ERROR_PCM_COMP:
return "PCM computation error";
case MccSimpleMovingControlsErrorCode::ERROR_GET_TELEMETRY:
return "cannot get telemetry";
case MccSimpleMovingControlsErrorCode::ERROR_DIST_TELEMETRY:
return "cannot get target-to-mount-position distance";
case MccSimpleMovingControlsErrorCode::ERROR_PZONE_CONTAINER_COMP:
return "pzone container computation error";
case MccSimpleMovingControlsErrorCode::ERROR_TARGET_IN_PZONE:
return "target is in prohibited zone";
case MccSimpleMovingControlsErrorCode::ERROR_NEAR_PZONE:
return "near prohibited zone";
case MccSimpleMovingControlsErrorCode::ERROR_TIMEOUT:
return "a timeout occured while slewing";
case MccSimpleMovingControlsErrorCode::ERROR_ALREADY_SLEW:
return "already slewing";
case MccSimpleMovingControlsErrorCode::ERROR_ALREADY_STOPPED:
return "slewing is already stopped";
case MccSimpleMovingControlsErrorCode::ERROR_STOPPED:
return "slewing was stopped";
default:
return "UNKNOWN";
}
}
static const MccSimpleMovingControlsCategory& get()
{
static const MccSimpleMovingControlsCategory constInst;
return constInst;
}
};
inline std::error_code make_error_code(MccSimpleMovingControlsErrorCode ec)
{
return std::error_code(static_cast<int>(ec), MccSimpleMovingControlsCategory::get());
}
class MccSimpleMovingControls
{
static constexpr auto DEG90INRADS = std::numbers::pi / 2.0;
class PathFile
{
public:
PathFile(const std::string& filename = "") : _filename(filename), _st() {}
void setFilename(const std::string& filename)
{
_filename = filename;
}
std::string getFilename() const
{
return _filename;
}
~PathFile()
{
save();
}
friend PathFile& operator<<(PathFile& pf, auto&& v)
{
pf._st << std::forward<decltype(v)>(v);
return pf;
}
bool save()
{
std::fstream fst;
if (_filename.empty()) {
return false;
}
if (_st.str().empty()) { // nothing to save
return true;
}
fst.open(_filename);
if (!fst.is_open()) {
return false;
}
fst << _st.str();
_st.str("");
_filename.clear();
return true;
}
private:
std::string _filename;
std::istringstream _st;
};
public:
typedef std::error_code error_t;
typedef MccSimpleMovingModelParams moving_params_t;
enum Mode { MOVING_MODE_SLEW, MOVING_MODE_TRACK, MOVING_MODE_ERROR };
// typedef std::CallbackFuncTion<void(Mode mode)> mode_switch_callback_t;
// protected:
// constexpr static auto defaultModeSwitchCallback = [](Mode) {};
// public:
template <mcc_generic_mount_c MountT,
std::invocable<typename MountT::mount_status_t> CallbackFuncT =
decltype([](typename MountT::mount_status_t) {})>
MccSimpleMovingControls(
MountT* mount,
CallbackFuncT&& mode_switch_callback = [](typename MountT::mount_status_t) {})
: _stopMoving(new std::atomic_bool), _currentParamsMutex(new std::mutex), _lastError(new std::atomic<error_t>)
{
auto send_to_hardware = [mount](typename MountT::hardware_state_t const& hw_state) {
mount->logDebug(std::format("Send to hardware: X = {} degs, Y = {} degs",
mcc::MccAngle{hw_state.X}.degrees(), mcc::MccAngle{hw_state.Y}.degrees()));
auto hw_err = mount->hardwareSetState(hw_state);
if (hw_err) {
return mcc_deduce_error_code(hw_err, MccSimpleMovingControlsErrorCode::ERROR_HW_SETSTATE);
}
mount->logDebug(" the 'hardwareSetState' method performed successfully!");
return MccSimpleMovingControlsErrorCode::ERROR_OK;
};
auto check_pzones = [mount, this](MccTelemetryData const& tdata, double min_time_to_pzone_in_secs,
double braking_accelX, double braking_accelY) {
bool in_zone;
std::vector<bool> in_zone_vec;
MccCelestialPoint cpt;
auto distXY = mcc_compute_distance(tdata, min_time_to_pzone_in_secs, braking_accelX, braking_accelY);
mount->logTrace(
std::format(" the distance that will be covered in the next {} seconds: X-axis: {}, Y-axis: {}",
min_time_to_pzone_in_secs, mcc::MccAngleFancyString(distXY.first),
mcc::MccAngleFancyString(distXY.second)));
// calculate coordinates at current speed '_currentParams.minTimeToPZone' seconds ahead
// and check them for getting into the prohibited zones
if constexpr (mccIsEquatorialMount(MountT::mountType)) {
cpt.X = tdata.HA + distXY.first;
cpt.Y = tdata.DEC_APP + distXY.second;
if (cpt.Y > DEG90INRADS) {
cpt.Y = DEG90INRADS;
}
if (cpt.Y < -DEG90INRADS) {
cpt.Y = -DEG90INRADS;
}
} else if constexpr (mccIsAltAzMount(MountT::mountType)) {
cpt.X = tdata.AZ + distXY.first;
cpt.Y = tdata.ZD + distXY.second;
if (cpt.Y < 0.0) {
cpt.Y = 0.0;
}
if (cpt.Y > std::numbers::pi) {
cpt.Y = std::numbers::pi;
}
}
mcc_tp2tp(tdata.time_point, cpt.time_point);
mount->logTrace(std::format(" mount: speedX = {}/s, speedY = {}/s",
mcc::MccAngleFancyString(tdata.speedX),
mcc::MccAngleFancyString(tdata.speedY)));
in_zone_vec.clear();
auto pz_err = mount->inPZone(cpt, &in_zone, &in_zone_vec);
if (pz_err) {
return mcc_deduce_error_code(pz_err, MccSimpleMovingControlsErrorCode::ERROR_PZONE_CONTAINER_COMP);
}
if (in_zone) {
size_t i = 0;
for (; i < in_zone_vec.size(); ++i) {
if (in_zone_vec[i]) {
break;
}
}
mount->logError("target point is near prohibited zone (zone index: {})! Entered target coordinates:",
i);
mount->logError(std::format(
" RA-APP, DEC-APP, HA, LST: {}, {}, {}, {}", mcc::MccAngle{tdata.RA_APP}.sexagesimal(true),
mcc::MccAngle{tdata.DEC_APP}.sexagesimal(), mcc::MccAngle{tdata.HA}.sexagesimal(true),
mcc::MccAngle{tdata.LST}.sexagesimal(true)));
mount->logError(std::format(" AZ, ZD, ALT: {}, {}, {}", mcc::MccAngle{tdata.AZ}.sexagesimal(),
mcc::MccAngle{tdata.ZD}.sexagesimal(),
mcc::MccAngle{tdata.ALT}.sexagesimal()));
mount->logError(std::format(" hardware X, Y: {}, {}", mcc::MccAngle{tdata.X}.sexagesimal(),
mcc::MccAngle{tdata.Y}.sexagesimal()));
return MccSimpleMovingControlsErrorCode::ERROR_NEAR_PZONE;
}
return MccSimpleMovingControlsErrorCode::ERROR_OK;
};
auto log_pos = [mount, this](typename MountT::hardware_state_t const& hw_state, MccTelemetryData const& tdata) {
if constexpr (mccIsEquatorialMount(MountT::mountType)) {
mount->logTrace(std::format(" current target: HA = {}, DEC = {}",
mcc::MccAngle(tdata.target.HA).sexagesimal(true),
mcc::MccAngle(tdata.target.DEC_APP).sexagesimal()));
mount->logTrace(std::format(" current mount: HA = {}, DEC = {}",
mcc::MccAngle(tdata.HA).sexagesimal(true),
mcc::MccAngle(tdata.DEC_APP).sexagesimal()));
_pathFile << tdata.time_point.time_since_epoch().count() << " " << tdata.target.HA << " "
<< tdata.target.DEC_APP << " " << tdata.HA << " " << tdata.DEC_APP << " "
<< (tdata.target.HA - tdata.HA) << " " << (tdata.target.DEC_APP - tdata.DEC_APP) << " "
<< (int)hw_state.moving_state << "\n";
} else if constexpr (mccIsAltAzMount(MountT::mountType)) {
mount->logTrace(std::format(" target: AZ = {}, ZD = {}",
mcc::MccAngle(tdata.target.AZ).sexagesimal(),
mcc::MccAngle(tdata.target.ZD).sexagesimal()));
mount->logTrace(std::format(" mount: AZ = {}, ZD = {}", mcc::MccAngle(tdata.AZ).sexagesimal(),
mcc::MccAngle(tdata.ZD).sexagesimal()));
_pathFile << tdata.time_point.time_since_epoch().count() << " " << tdata.target.AZ << " "
<< tdata.target.ZD << " " << tdata.AZ << " " << tdata.ZD << " "
<< (tdata.target.AZ - tdata.AZ) << " " << (tdata.target.ZD - tdata.ZD) << " "
<< (int)hw_state.moving_state << "\n";
}
};
*_stopMoving = true;
*_lastError = MccSimpleMovingControlsErrorCode::ERROR_OK;
using cb_func_t = std::function<void(typename MountT::mount_status_t)>;
auto cb_sptr = std::shared_ptr<cb_func_t>(new cb_func_t(std::forward<CallbackFuncT>(mode_switch_callback)));
/* stop moving function */
_stopMovingFunc = [mount, this]() {
typename MountT::hardware_state_t hw_state;
hw_state.moving_state == MountT::hardware_moving_state_t::HW_MOVE_STOPPED;
*_stopMoving = true;
*_lastError = send_to_hardware(hw_state);
};
/* slewing function */
_slewingFunc = [mount, cb_sptr, send_to_hardware, check_pzones, log_pos, this](bool slew_and_stop) {
double braking_accelX, braking_accelY;
double min_time_to_pzone_in_secs;
{
// std::lock_guard lock{*_currentParamsMutex};
if (mcc::utils::isEqual(_currentParams.brakingAccelX, 0.0)) {
braking_accelX = std::numeric_limits<double>::min();
} else {
braking_accelX = std::abs(_currentParams.brakingAccelX);
}
if (mcc::utils::isEqual(_currentParams.brakingAccelY, 0.0)) {
braking_accelY = std::numeric_limits<double>::min();
} else {
braking_accelY = std::abs(_currentParams.brakingAccelY);
}
min_time_to_pzone_in_secs =
std::chrono::duration_cast<std::chrono::duration<double>>(_currentParams.minTimeToPZone).count();
if (!_currentParams.slewingPathFilename.empty()) { // open slewing trajectory file
_pathFile.setFilename(_currentParams.slewingPathFilename);
} else {
mount->logError("Slewing path filename is empty! Do not save it!");
}
}
mount->logInfo(
std::format("Start slewing in mode '{}'", (slew_and_stop ? "SLEW-AND-STOP" : "SLEW-AND-TRACK")));
mount->logInfo(std::format(" slewing process timeout: {} secs", _currentParams.slewTimeout.count()));
if (!slew_and_stop) {
mount->logInfo(std::format(" slewing tolerance radius: {} arcsecs",
mcc::MccAngle{_currentParams.slewToleranceRadius}.arcsecs()));
}
mount->logInfo(std::format(" braking acceleration X: {} degs/s^2 (in config: {} rads/s^2)",
mcc::MccAngle(braking_accelX).degrees(), _currentParams.brakingAccelX));
mount->logInfo(std::format(" braking acceleration Y: {} degs/s^2 (in config: {} rads/s^2)",
mcc::MccAngle(braking_accelY).degrees(), _currentParams.brakingAccelY));
mount->logInfo(std::format(" min time to prohibited zone: {} seconds", min_time_to_pzone_in_secs));
_pathFile << "# \n";
_pathFile << "# Slewing trajectory, " << std::chrono::system_clock::now() << "\n";
_pathFile << "# Config:\n";
_pathFile << "# slewing tolerance radius: "
<< mcc::MccAngle{_currentParams.slewToleranceRadius}.arcsecs() << " arcsecs\n";
_pathFile << "# slewing process timeout: " << _currentParams.slewTimeout.count() << " secs\n";
_pathFile << "# \n";
_pathFile << "# Format (time is in nanoseconds, coordinates are in radians): \n";
_pathFile << "# <UNIXTIME> <target X> <target Y> <mount X> <mount Y> <dX_{target-mount}> "
"<dY_{target-mount}> <moving state>\n";
typename MountT::error_t t_err;
MccTelemetryData tdata;
{
std::lock_guard lock{*_currentParamsMutex};
t_err = mount->telemetryData(&tdata);
if (t_err) {
*_lastError = mcc_deduce_error_code(t_err, MccSimpleMovingControlsErrorCode::ERROR_GET_TELEMETRY);
return;
}
}
auto last_hw_time = tdata.time_point;
// bool in_zone;
// std::vector<bool> in_zone_vec;
// MccCelestialPoint cpt;
// if constexpr (mccIsEquatorialMount(MountT::mountType)) {
// cpt.pair_kind = MccCoordPairKind::COORDS_KIND_HADEC_APP;
// } else if constexpr (mccIsAltAzMount(MountT::mountType)) {
// cpt.pair_kind = MccCoordPairKind::COORDS_KIND_AZZD;
// } else {
// static_assert(false, "UNKNOWN MOUNT TYPE!");
// }
typename MountT::hardware_state_t hw_state;
auto hw_err = mount->hardwareGetState(&hw_state);
if (hw_err) {
*_stopMoving = true;
*_lastError = mcc_deduce_error_code(hw_err, MccSimpleMovingControlsErrorCode::ERROR_HW_GETSTATE);
return;
}
hw_state.X = (double)tdata.target.X;
hw_state.Y = (double)tdata.target.Y;
{
std::lock_guard lock{*_currentParamsMutex};
hw_state.speedX = _currentParams.slewRateX;
hw_state.speedY = _currentParams.slewRateY;
}
hw_state.moving_state = MountT::hardware_moving_state_t::HW_MOVE_SLEWING;
// start slewing ...
error_t err = send_to_hardware(hw_state);
if (err) {
*_lastError = err;
mount->logError(std::format("start slewing: an error occured while sending hardware state: {} {} {}",
err.value(), err.category().name(), err.message()));
return;
}
*cb_sptr(MountT::mount_status_t::SLEWING); // send the status to the mount
double dist;
std::chrono::steady_clock::time_point start_slewing_tp, last_adjust_tp;
start_slewing_tp = std::chrono::steady_clock::now();
last_adjust_tp = start_slewing_tp;
std::pair<double, double> distXY;
bool tag_var_coord = true;
if (tdata.target.pair_kind == MccCoordPairKind::COORDS_KIND_AZALT ||
tdata.target.pair_kind == MccCoordPairKind::COORDS_KIND_AZZD) {
tag_var_coord = false;
}
auto start_point = tdata.time_point; // needed for trajectory file
// main loop (simply monitors the current position taking into account the prohibited zones, as well as the
// timeout of the entire process)
while (!*_stopMoving) {
// wait for updated telemetry data
{
std::lock_guard lock{*_currentParamsMutex};
t_err = mount->waitForTelemetryData(&tdata, _currentParams.telemetryTimeout);
if (t_err) {
*_lastError =
mcc_deduce_error_code(t_err, MccSimpleMovingControlsErrorCode::ERROR_GET_TELEMETRY);
break;
}
last_hw_time = tdata.time_point;
}
hw_err = mount->hardwareGetState(&hw_state);
if (hw_err) {
*_lastError = mcc_deduce_error_code(hw_err, MccSimpleMovingControlsErrorCode::ERROR_HW_GETSTATE);
break;
}
log_pos(hw_state, tdata);
if (*_stopMoving) {
*_lastError = MccSimpleMovingControlsErrorCode::ERROR_STOPPED;
break;
}
err = check_pzones(tdata, min_time_to_pzone_in_secs, braking_accelX, braking_accelY);
if (err) {
*_lastError = err;
break;
}
{
std::lock_guard lock{*_currentParamsMutex};
if ((std::chrono::steady_clock::now() - start_slewing_tp) > _currentParams.slewTimeout) {
mount->logError("slewing process timeout!");
*_lastError = MccSimpleMovingControlsErrorCode::ERROR_TIMEOUT;
break;
}
}
if (slew_and_stop && !tag_var_coord) { // just wait for mount to be stopped
if (hw_state.moving_state == MountT::hardware_moving_state_t::HW_MOVE_STOPPED) {
mount->logInfo("mount moving state is STOPPED - exit!");
break;
}
} else {
if (last_hw_time == tdata.time_point) {
mount->logTrace("Same hardware timepoint! Just continue to polling!\n\n\n\n");
continue;
}
last_hw_time = tdata.time_point;
t_err = mount->targetToMountDist(&dist);
if (t_err) {
*_lastError =
mcc_deduce_error_code(t_err, MccSimpleMovingControlsErrorCode::ERROR_DIST_TELEMETRY);
break;
}
mount->logTrace(std::format(" target-to-mount distance: {}", mcc::MccAngleFancyString(dist)));
if ((dist <= _currentParams.slewToleranceRadius) &&
(hw_state.moving_state ==
MountT::hardware_moving_state_t::HW_MOVE_GUIDING)) { // stop slewing and exit from
// cycle
mount->logInfo("target-to-mount distance is lesser than slew tolerance radius - exit!");
if (slew_and_stop) {
stopMount();
}
break;
}
if (*_stopMoving) {
*_lastError = MccSimpleMovingControlsErrorCode::ERROR_STOPPED;
break;
// return MccSimpleMovingControlsErrorCode::ERROR_STOPPED;
}
// resend new position since target coordinates are changed in time
hw_state.X = (double)tdata.target.X;
hw_state.Y = (double)tdata.target.Y;
err = send_to_hardware(hw_state);
if (err) {
*_lastError = err;
break;
}
}
if (*_stopMoving) {
*_lastError = MccSimpleMovingControlsErrorCode::ERROR_STOPPED;
break;
}
// sleep here
std::this_thread::sleep_for(_currentParams.slewingTelemetryInterval);
}
*_stopMoving = true;
mount->logInfo("Slewing finished");
err = *_lastError;
mount->logInfo(std::format(" exit code: {} {} {}", err.value(), err.category().name(), err.message()));
_pathFile.save();
// get final position
if (!err) {
// wait for updated telemetry data
{
std::lock_guard lock{*_currentParamsMutex};
t_err = mount->waitForTelemetryData(&tdata, _currentParams.telemetryTimeout);
if (t_err) {
*_lastError =
mcc_deduce_error_code(t_err, MccSimpleMovingControlsErrorCode::ERROR_GET_TELEMETRY);
return;
}
}
t_err = mount->targetToMountDist(&dist);
if (t_err) {
*_lastError = mcc_deduce_error_code(t_err, MccSimpleMovingControlsErrorCode::ERROR_DIST_TELEMETRY);
return;
}
log_pos(hw_state, tdata);
mount->logDebug(std::format(" target-to-mount distance {}", mcc::MccAngleFancyString(dist)));
if (!slew_and_stop) { // start tracking
_trackingFunc();
} else {
*_lastError = MccSimpleMovingControlsErrorCode::ERROR_OK;
}
}
};
/* tracking function */
_trackingFunc = [mount, cb_sptr, check_pzones, send_to_hardware, log_pos, this]() {
double braking_accelX, braking_accelY;
double min_time_to_pzone_in_secs;
{
// std::lock_guard lock{*_currentParamsMutex};
if (mcc::utils::isEqual(_currentParams.brakingAccelX, 0.0)) {
braking_accelX = std::numeric_limits<double>::min();
} else {
braking_accelX = std::abs(_currentParams.brakingAccelX);
}
if (mcc::utils::isEqual(_currentParams.brakingAccelY, 0.0)) {
braking_accelY = std::numeric_limits<double>::min();
} else {
braking_accelY = std::abs(_currentParams.brakingAccelY);
}
min_time_to_pzone_in_secs =
std::chrono::duration_cast<std::chrono::duration<double>>(_currentParams.minTimeToPZone).count();
if (!_currentParams.trackingPathFilename.empty()) { // open slewing trajectory file
_pathFile.setFilename(_currentParams.trackingPathFilename);
} else {
mount->logError("Tracking path filename is empty! Do not save it!");
}
}
mount->logInfo("Start tracking");
mount->logInfo(std::format(" braking acceleration X: {} degs/s^2 (in config: {} rads/s^2)",
mcc::MccAngle(braking_accelX).degrees(), _currentParams.brakingAccelX));
mount->logInfo(std::format(" braking acceleration Y: {} degs/s^2 (in config: {} rads/s^2)",
mcc::MccAngle(braking_accelY).degrees(), _currentParams.brakingAccelY));
mount->logInfo(std::format(" min time to prohibited zone: {} seconds", min_time_to_pzone_in_secs));
_pathFile << "# \n";
_pathFile << "# Tracking trajectory, " << std::chrono::system_clock::now() << "\n";
_pathFile << "# \n";
_pathFile << "# Format (time is in nanoseconds, coordinates are in radians): \n";
_pathFile << "# <UNIXTIME> <target X> <target Y> <mount X> <mount Y> <dX_{target-mount}> "
"<dY_{target-mount}> <moving state>\n";
typename MountT::hardware_state_t hw_state;
error_t err;
MccTelemetryData tdata;
double dist;
auto last_hw_time = tdata.time_point;
*cb_sptr(MountT::mount_status_t::TRACKING); // send the status to the mount
while (!*_stopMoving) {
// wait for updated telemetry data
{
std::lock_guard lock{*_currentParamsMutex};
auto t_err = mount->waitForTelemetryData(&tdata, _currentParams.telemetryTimeout);
if (t_err) {
*_lastError =
mcc_deduce_error_code(t_err, MccSimpleMovingControlsErrorCode::ERROR_GET_TELEMETRY);
break;
}
last_hw_time = tdata.time_point;
}
auto hw_err = mount->hardwareGetState(&hw_state);
if (hw_err) {
*_lastError = mcc_deduce_error_code(hw_err, MccSimpleMovingControlsErrorCode::ERROR_HW_GETSTATE);
break;
}
log_pos(hw_state, tdata);
if (*_stopMoving) {
*_lastError = MccSimpleMovingControlsErrorCode::ERROR_STOPPED;
break;
}
err = check_pzones(tdata, min_time_to_pzone_in_secs, braking_accelX, braking_accelY);
if (err) {
*_lastError = err;
break;
}
if (last_hw_time == tdata.time_point) {
mount->logTrace("Same hardware timepoint! Just continue to polling!\n\n\n\n");
continue;
}
last_hw_time = tdata.time_point;
auto t_err = mount->targetToMountDist(&dist);
if (t_err) {
*_lastError = mcc_deduce_error_code(t_err, MccSimpleMovingControlsErrorCode::ERROR_DIST_TELEMETRY);
break;
}
mount->logTrace(std::format(" target-to-mount distance: {}", mcc::MccAngleFancyString(dist)));
// resend new position since target coordinates are changed in time
hw_state.X = (double)tdata.target.X;
hw_state.Y = (double)tdata.target.Y;
err = send_to_hardware(hw_state);
if (err) {
*_lastError = err;
break;
}
if (*_stopMoving) {
*_lastError = MccSimpleMovingControlsErrorCode::ERROR_STOPPED;
break;
}
// sleep here
std::this_thread::sleep_for(_currentParams.trackingTelemetryInterval);
}
*_stopMoving = true;
mount->logInfo("Tracking finished");
err = *_lastError;
mount->logInfo(std::format(" exit code: {} {} {}", err.value(), err.category().name(), err.message()));
_pathFile.save();
};
}
virtual ~MccSimpleMovingControls()
{
*_stopMoving = true;
}
error_t slewToTarget(bool slew_and_stop = false)
{
return *_lastError;
}
error_t trackTarget()
{
return *_lastError;
}
error_t stopMount()
{
if (*_stopMoving) {
*_lastError = MccSimpleMovingControlsErrorCode::ERROR_ALREADY_STOPPED;
} else {
_stopMovingFunc();
}
return *_lastError;
}
error_t setMovingParams(moving_params_t params)
{
std::lock_guard lock{*_currentParamsMutex};
_currentParams = std::move(params);
return MccSimpleMovingControlsErrorCode::ERROR_OK;
}
moving_params_t getMovingParams() const
{
std::lock_guard lock{*_currentParamsMutex};
return _currentParams;
}
error_t mountMovingLastError() const
{
return *_lastError;
}
protected:
std::function<void(bool)> _slewingFunc{};
std::function<void()> _trackingFunc{};
std::function<void()> _stopMovingFunc{};
std::unique_ptr<std::atomic_bool> _stopMoving;
std::unique_ptr<std::mutex> _currentParamsMutex;
moving_params_t _currentParams{};
std::unique_ptr<std::atomic<error_t>> _lastError;
PathFile _pathFile{};
};
} // namespace mcc

View File

@@ -59,6 +59,9 @@ struct MccSimpleMovingModelParams {
double brakingAccelX{0.0}; double brakingAccelX{0.0};
double brakingAccelY{0.0}; double brakingAccelY{0.0};
// slewing trajectory file. if empty - just skip saving
std::string slewingPathFilename{};
// ******* tracking mode ******* // ******* tracking mode *******
@@ -76,6 +79,10 @@ struct MccSimpleMovingModelParams {
// it it is greater then the current mount coordinates are used as target one // it it is greater then the current mount coordinates are used as target one
double trackingMaxCoordDiff{20.0}; double trackingMaxCoordDiff{20.0};
// tracking trajectory file. if empty - just skip saving
std::string trackingPathFilename{};
// ******* guiding mode ******* // ******* guiding mode *******
double guidingCorrectionRange[2]{0.3_arcsecs, 3.0_arcsecs}; double guidingCorrectionRange[2]{0.3_arcsecs, 3.0_arcsecs};

View File

@@ -23,6 +23,7 @@
#include <asio/signal_set.hpp> #include <asio/signal_set.hpp>
#include <asio/steady_timer.hpp> #include <asio/steady_timer.hpp>
#include <asio/streambuf.hpp> #include <asio/streambuf.hpp>
#include <asio/thread_pool.hpp>
#include <asio/write.hpp> #include <asio/write.hpp>
#include <spdlog/sinks/null_sink.h> #include <spdlog/sinks/null_sink.h>
@@ -181,12 +182,13 @@ public:
_asioContext(ctx), _asioContext(ctx),
_handleMessageFunc(func), _handleMessageFunc(func),
_stopSignal(ctx), _stopSignal(ctx),
_restartSignal(ctx) _restartSignal(ctx),
_sessionThreadPool(7)
{ {
std::stringstream st; std::stringstream st;
st << std::this_thread::get_id(); st << std::this_thread::get_id();
logInfo(std::format("Create generic network server instance (thread ID = {})", st.str())); logInfo(std::format("Create MccGenericNetworkServer class instance (thread ID = {})", st.str()));
} }
// MccNetworkServer(asio::io_context& ctx, const handle_message_func_t& func, LoggerT logger = MccNullLogger{}) // MccNetworkServer(asio::io_context& ctx, const handle_message_func_t& func, LoggerT logger = MccNullLogger{})
@@ -206,6 +208,10 @@ public:
logInfo(std::format("Delete generic network server instance (thread ID = {}) ...", st.str())); logInfo(std::format("Delete generic network server instance (thread ID = {}) ...", st.str()));
stopListening(); stopListening();
_sessionThreadPool.stop();
_sessionThreadPool.join();
disconnectClients(); disconnectClients();
} }
@@ -359,10 +365,21 @@ public:
// start accepting connections // start accepting connections
for (;;) { for (;;) {
st.str("");
st << std::this_thread::get_id();
logDebug(std::format("Start accepting new connections (thread ID = {}) ...", st.str()));
auto sock = co_await acc.async_accept(asio::use_awaitable); auto sock = co_await acc.async_accept(asio::use_awaitable);
// start new client session // start new client session
asio::co_spawn(_asioContext, startSession(std::move(sock)), asio::detached); logDebug("Spawn new user session ...");
logDebug(std::format("session was spawned, start accepting new connections ..."));
// asio::co_spawn(_asioContext, startSession(std::move(sock)), asio::detached);
// use of sessions own thread pool
asio::co_spawn(_sessionThreadPool, startSession(std::move(sock)), asio::detached);
logDebug("The session was spawned");
} }
@@ -572,8 +589,15 @@ public:
_stopSignal.async_wait([this](std::error_code, int signo) { _stopSignal.async_wait([this](std::error_code, int signo) {
logInfo(std::format("Stop signal was received (signo = {})", signo)); logInfo(std::format("Stop signal was received (signo = {})", signo));
stopListening(); // _handleMessageFunc(MCC_COMMPROTO_KEYWORD_STOP_STR);
disconnectClients(); // std::this_thread::sleep_for(std::chrono::milliseconds(100));
// stopListening();
// _sessionThreadPool.stop();
// _sessionThreadPool.join();
// disconnectClients();
_asioContext.stop(); _asioContext.stop();
}); });
@@ -621,6 +645,8 @@ protected:
std::mutex _serialPortsMutex, _tcpSocketsMutex, _localStreamSocketsMutex, _localSeqpackSocketsMutex; std::mutex _serialPortsMutex, _tcpSocketsMutex, _localStreamSocketsMutex, _localSeqpackSocketsMutex;
asio::thread_pool _sessionThreadPool;
// helpers // helpers
template <typename OptT, typename... OptTs> template <typename OptT, typename... OptTs>
void setSerialOpts(asio::serial_port& s_port, OptT&& opt, OptTs&&... opts) void setSerialOpts(asio::serial_port& s_port, OptT&& opt, OptTs&&... opts)
@@ -898,10 +924,21 @@ class MccGenericMountNetworkServer : public MccGenericNetworkServer<LoggerT>
public: public:
using typename base_t::handle_message_func_result_t; using typename base_t::handle_message_func_result_t;
using base_t::logDebug;
using base_t::logError;
using base_t::logInfo;
using base_t::logTrace;
using base_t::logWarn;
template <mcc_generic_mount_c MountT, typename... LoggerCtorArgsTs> template <mcc_generic_mount_c MountT, typename... LoggerCtorArgsTs>
MccGenericMountNetworkServer(asio::io_context& ctx, MountT& mount, LoggerCtorArgsTs&&... log_args) MccGenericMountNetworkServer(asio::io_context& ctx, MountT& mount, LoggerCtorArgsTs&&... log_args)
: base_t(ctx, {}, std::forward<LoggerCtorArgsTs>(log_args)...) : base_t(ctx, {}, std::forward<LoggerCtorArgsTs>(log_args)...)
{ {
std::stringstream st;
st << std::this_thread::get_id();
logInfo(std::format("Create MccGenericMountNetworkServer class instance (thread ID = {})", st.str()));
// to avoid possible compiler optimization (one needs to catch 'mount' strictly by reference) // to avoid possible compiler optimization (one needs to catch 'mount' strictly by reference)
auto* mount_ptr = &mount; auto* mount_ptr = &mount;
@@ -924,15 +961,27 @@ public:
return output_msg.template byteRepr<typename base_t::handle_message_func_result_t>(); return output_msg.template byteRepr<typename base_t::handle_message_func_result_t>();
}; };
// special functor (used in the destructor)
_stopMountFunc = [mount_ptr]() { mount_ptr->stopMount(); };
} }
virtual ~MccGenericMountNetworkServer() {} virtual ~MccGenericMountNetworkServer()
{
std::stringstream st;
st << std::this_thread::get_id();
_stopMountFunc();
logInfo(std::format("Delete MccGenericMountNetworkServer class instance (thread ID = {})", st.str()));
}
protected: protected:
MccCoordinateSerializer::SerializedCoordFormat _coordFormat{ MccCoordinateSerializer::SerializedCoordFormat _coordFormat{
MccCoordinateSerializer::SerializedCoordFormat::CFMT_SGM}; MccCoordinateSerializer::SerializedCoordFormat::CFMT_SGM};
MccCoordinateSerializer::SexagesimalCoordPrec _coordPrec{2, 1}; MccCoordinateSerializer::SexagesimalCoordPrec _coordPrec{2, 1};
std::function<void()> _stopMountFunc{};
template <typename RESULT_MSG_T, typename INPUT_MSG_T, mcc_generic_mount_c MountT> template <typename RESULT_MSG_T, typename INPUT_MSG_T, mcc_generic_mount_c MountT>
RESULT_MSG_T handleMessage(const INPUT_MSG_T& input_msg, MountT* mount_ptr) RESULT_MSG_T handleMessage(const INPUT_MSG_T& input_msg, MountT* mount_ptr)
@@ -1037,34 +1086,6 @@ protected:
err = vc.error(); err = vc.error();
} }
} }
// auto vc = input_msg.template paramValue<MccCelestialPoint>(0); // is it set operation?
// if (vc) { // coordinates are given - set
// operation
// auto m_err = mount_ptr->setPointingTarget(vc.value());
// if (m_err) {
// if (m_err) {
// err = mcc_deduce_error_code(m_err,
// MccGenericMountNetworkServerErrorCode::ERROR_MOUNT_SET_TARGET);
// }
// } else {
// output_msg.construct(MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR, input_msg.byteRepr());
// }
// } else {
// auto vp = input_msg.template paramValue<MccCoordPairKind>(0);
// if (vp) { // coordinate pair kind is given
// cp.pair_kind = vp.value();
// err = coordsFromTelemetryData(*mount_ptr, true, cp);
// if (!err) {
// output_msg.construct(MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR,
// MCC_COMMPROTO_KEYWORD_TARGET_STR,
// _coordFormat, _coordPrec, cp);
// }
// } else { // invalid command!!!
// err = vp.error();
// }
// }
} else { // get operation } else { // get operation
err = coordsFromTelemetryData(*mount_ptr, true, cp); err = coordsFromTelemetryData(*mount_ptr, true, cp);
if (!err) { if (!err) {
@@ -1100,7 +1121,8 @@ protected:
} else if (input_msg.withKey(MCC_COMMPROTO_KEYWORD_TELEMETRY_STR)) { } else if (input_msg.withKey(MCC_COMMPROTO_KEYWORD_TELEMETRY_STR)) {
MccTelemetryData tdata; MccTelemetryData tdata;
auto t_err = mount_ptr->telemetryData(&tdata); // auto t_err = mount_ptr->telemetryData(&tdata);
auto t_err = mount_ptr->waitForTelemetryData(&tdata);
if (t_err) { if (t_err) {
err = mcc_deduce_error_code(t_err, MccGenericMountNetworkServerErrorCode::ERROR_MOUNT_GET_TELEMETRY); err = mcc_deduce_error_code(t_err, MccGenericMountNetworkServerErrorCode::ERROR_MOUNT_GET_TELEMETRY);
} else { } else {
@@ -1150,7 +1172,8 @@ protected:
{ {
MccTelemetryData tdata; MccTelemetryData tdata;
auto t_err = mount.telemetryData(&tdata); auto t_err = mount.waitForTelemetryData(&tdata);
// auto t_err = mount.telemetryData(&tdata);
if (t_err) { if (t_err) {
return mcc_deduce_error_code(t_err, MccGenericMountNetworkServerErrorCode::ERROR_MOUNT_GET_TELEMETRY); return mcc_deduce_error_code(t_err, MccGenericMountNetworkServerErrorCode::ERROR_MOUNT_GET_TELEMETRY);
} }

View File

@@ -105,7 +105,7 @@ public:
typedef double coord_t; typedef double coord_t;
// "classic" geometric PEC coefficients // "classic" geometric PCM coefficients
struct pcm_geom_coeffs_t { struct pcm_geom_coeffs_t {
typedef double coeff_t; typedef double coeff_t;

View File

@@ -6,6 +6,8 @@
/* SIMPLE SLEWING MODEL IMPLEMENTATION */ /* SIMPLE SLEWING MODEL IMPLEMENTATION */
#include <fstream>
#include "mcc_defaults.h" #include "mcc_defaults.h"
#include "mcc_generics.h" #include "mcc_generics.h"
#include "mcc_moving_model_common.h" #include "mcc_moving_model_common.h"
@@ -85,6 +87,8 @@ struct MccSimpleSlewingModelCategory : public std::error_category {
return "already slewing"; return "already slewing";
case MccSimpleSlewingModelErrorCode::ERROR_ALREADY_STOPPED: case MccSimpleSlewingModelErrorCode::ERROR_ALREADY_STOPPED:
return "slewing is already stopped"; return "slewing is already stopped";
case MccSimpleSlewingModelErrorCode::ERROR_STOPPED:
return "slewing was stopped";
default: default:
return "UNKNOWN"; return "UNKNOWN";
} }
@@ -120,30 +124,54 @@ public:
template <mcc_all_controls_c CONTROLS_T, mcc_logger_c LoggerT = MccNullLogger> template <mcc_all_controls_c CONTROLS_T, mcc_logger_c LoggerT = MccNullLogger>
MccSimpleSlewingModel(CONTROLS_T* controls, LoggerT logger) MccSimpleSlewingModel(CONTROLS_T* controls, LoggerT logger)
: _stopSlewing(new std::atomic_bool()), _currentParamsMutex(new std::mutex) : _stopSlewing(new std::atomic_bool()),
_currentParamsMutex(new std::mutex),
_lastError(new std::atomic<error_t>())
{ {
logger.logDebug("Create MccSimpleSlewingModel class instance"); std::ostringstream os;
os << std::this_thread::get_id();
logger.logDebug(std::format("Create MccSimpleSlewingModel class instance (thread: {})", os.str()));
*_stopSlewing = true; *_stopSlewing = true;
*_lastError = MccSimpleSlewingModelErrorCode::ERROR_OK;
_checkTargetFunc = [controls, logger, this]() mutable -> error_t { _checkTargetFunc = [controls, logger, this]() mutable -> error_t {
typename CONTROLS_T::error_t t_err; typename CONTROLS_T::error_t t_err;
MccTelemetryData tdata; MccTelemetryData tdata;
bool in_zone; bool in_zone;
std::vector<bool> in_zone_vec;
t_err = controls->telemetryData(&tdata);
// t_err = controls->telemetryData(&tdata);
{
std::lock_guard lock{*_currentParamsMutex};
t_err = controls->waitForTelemetryData(&tdata);
if (t_err) {
return *_lastError =
mcc_deduce_error_code(t_err, MccSimpleSlewingModelErrorCode::ERROR_GET_TELEMETRY);
}
}
if (t_err) { if (t_err) {
return mcc_deduce_error_code(t_err, MccSimpleSlewingModelErrorCode::ERROR_GET_TELEMETRY); return mcc_deduce_error_code(t_err, MccSimpleSlewingModelErrorCode::ERROR_GET_TELEMETRY);
} }
auto pz_err = controls->inPZone(tdata.target, &in_zone); auto pz_err = controls->inPZone(tdata.target, &in_zone, &in_zone_vec);
if (pz_err) { if (pz_err) {
return mcc_deduce_error_code(pz_err, MccSimpleSlewingModelErrorCode::ERROR_PZONE_CONTAINER_COMP); return mcc_deduce_error_code(pz_err, MccSimpleSlewingModelErrorCode::ERROR_PZONE_CONTAINER_COMP);
} }
if (in_zone) { if (in_zone) {
logger.logError("target point is in prohibited zone! Entered target coordinates:"); size_t i = 0;
for (; i < in_zone_vec.size(); ++i) {
if (in_zone_vec[i]) {
break;
}
}
logger.logError("target point is in prohibited zone (zone index: {})! Entered target coordinates:", i);
logger.logError(std::format(" RA-APP, DEC-APP, HA, LST: {}, {}, {}, {}", logger.logError(std::format(" RA-APP, DEC-APP, HA, LST: {}, {}, {}, {}",
mcc::MccAngle{tdata.target.RA_APP}.sexagesimal(true), mcc::MccAngle{tdata.target.RA_APP}.sexagesimal(true),
mcc::MccAngle{tdata.target.DEC_APP}.sexagesimal(), mcc::MccAngle{tdata.target.DEC_APP}.sexagesimal(),
@@ -153,6 +181,10 @@ public:
mcc::MccAngle{tdata.target.ZD}.sexagesimal(), mcc::MccAngle{tdata.target.ZD}.sexagesimal(),
mcc::MccAngle{tdata.target.ALT}.sexagesimal())); mcc::MccAngle{tdata.target.ALT}.sexagesimal()));
logger.logError(std::format(" hardware X, Y: {}, {}", mcc::MccAngle{tdata.target.X}.sexagesimal(),
mcc::MccAngle{tdata.target.Y}.sexagesimal()));
return MccSimpleSlewingModelErrorCode::ERROR_TARGET_IN_PZONE; return MccSimpleSlewingModelErrorCode::ERROR_TARGET_IN_PZONE;
} }
@@ -160,47 +192,15 @@ public:
}; };
_slewingFunc = [controls, logger = std::move(logger), this](bool slew_and_stop) mutable -> error_t { _slewingFunc = [controls, logger = std::move(logger), this](bool slew_and_stop) mutable -> error_t {
// first, check target coordinates // reset error
typename CONTROLS_T::error_t t_err; *_lastError = MccSimpleSlewingModelErrorCode::ERROR_OK;
MccTelemetryData tdata;
{
std::lock_guard lock{*_currentParamsMutex};
t_err = controls->telemetryData(&tdata);
if (t_err) {
return mcc_deduce_error_code(t_err, MccSimpleSlewingModelErrorCode::ERROR_GET_TELEMETRY);
}
}
bool in_zone;
auto pz_err = controls->inPZone(tdata.target, &in_zone);
if (pz_err) {
*_stopSlewing = true;
return mcc_deduce_error_code(pz_err, MccSimpleSlewingModelErrorCode::ERROR_PZONE_CONTAINER_COMP);
}
if (in_zone) {
*_stopSlewing = true;
logger.logError("target point is in prohibited zone! Entered target coordinates:");
logger.logError(std::format(" RA-APP, DEC-APP, HA, LST: {}, {}, {}, {}",
mcc::MccAngle{tdata.target.RA_APP}.sexagesimal(true),
mcc::MccAngle{tdata.target.DEC_APP}.sexagesimal(),
mcc::MccAngle{tdata.target.HA}.sexagesimal(true),
mcc::MccAngle{tdata.LST}.sexagesimal(true)));
logger.logError(std::format(" AZ, ZD, ALT: {}, {}, {}", mcc::MccAngle{tdata.target.AZ}.sexagesimal(),
mcc::MccAngle{tdata.target.ZD}.sexagesimal(),
mcc::MccAngle{tdata.target.ALT}.sexagesimal()));
return MccSimpleSlewingModelErrorCode::ERROR_TARGET_IN_PZONE;
}
if (*_stopSlewing) {
return MccSimpleSlewingModelErrorCode::ERROR_STOPPED;
}
double braking_accelX, braking_accelY; double braking_accelX, braking_accelY;
double min_time_to_pzone_in_secs;
bool store_path = false;
std::ofstream fst;
using path_tp_t = std::chrono::duration<double>; // seconds represented as double
{ {
// std::lock_guard lock{*_currentParamsMutex}; // std::lock_guard lock{*_currentParamsMutex};
@@ -215,6 +215,19 @@ public:
} else { } else {
braking_accelY = std::abs(_currentParams.brakingAccelY); braking_accelY = std::abs(_currentParams.brakingAccelY);
} }
min_time_to_pzone_in_secs =
std::chrono::duration_cast<std::chrono::duration<double>>(_currentParams.minTimeToPZone).count();
if (!_currentParams.slewingPathFilename.empty()) { // open slewing trajectory file
fst.open(_currentParams.slewingPathFilename);
if (fst.is_open()) {
store_path = true;
} else {
logger.logError(std::format("Cannot open slewing path file: {}! Do not save it!",
_currentParams.slewingPathFilename));
}
}
} }
logger.logInfo( logger.logInfo(
@@ -228,9 +241,42 @@ public:
mcc::MccAngle(braking_accelX).degrees(), _currentParams.brakingAccelX)); mcc::MccAngle(braking_accelX).degrees(), _currentParams.brakingAccelX));
logger.logInfo(std::format(" braking acceleration Y: {} degs/s^2 (in config: {} rads/s^2)", logger.logInfo(std::format(" braking acceleration Y: {} degs/s^2 (in config: {} rads/s^2)",
mcc::MccAngle(braking_accelY).degrees(), _currentParams.brakingAccelY)); mcc::MccAngle(braking_accelY).degrees(), _currentParams.brakingAccelY));
logger.logInfo(std::format(" min time to prohibited zone: {} seconds", min_time_to_pzone_in_secs));
if (store_path) {
fst << "# \n";
fst << "# Slewing trajectory, " << std::chrono::system_clock::now() << "\n";
fst << "# Config:\n";
fst << "# slewing tolerance radius: " << mcc::MccAngle{_currentParams.slewToleranceRadius}.arcsecs()
<< " arcsecs\n";
fst << "# slewing process timeout: " << _currentParams.slewTimeout.count() << " secs\n";
fst << "# \n";
fst << "# Format (time is in seconds, coordinates are in radians): \n";
fst << "# <time-since-start> <target X> <target Y> <mount X> <mount Y> <dX_{target-mount}> "
"<dY_{target-mount}> <moving state>\n";
}
typename CONTROLS_T::error_t t_err;
MccTelemetryData tdata;
{
std::lock_guard lock{*_currentParamsMutex};
t_err = controls->telemetryData(&tdata);
if (t_err) {
return *_lastError =
mcc_deduce_error_code(t_err, MccSimpleSlewingModelErrorCode::ERROR_GET_TELEMETRY);
}
}
auto last_hw_time = tdata.time_point;
bool in_zone;
std::vector<bool> in_zone_vec;
MccCelestialPoint cpt; MccCelestialPoint cpt;
double min_time_to_pzone_in_secs;
if constexpr (mccIsEquatorialMount(CONTROLS_T::mountType)) { if constexpr (mccIsEquatorialMount(CONTROLS_T::mountType)) {
cpt.pair_kind = MccCoordPairKind::COORDS_KIND_HADEC_APP; cpt.pair_kind = MccCoordPairKind::COORDS_KIND_HADEC_APP;
@@ -245,30 +291,34 @@ public:
auto hw_err = controls->hardwareGetState(&hw_state); auto hw_err = controls->hardwareGetState(&hw_state);
if (hw_err) { if (hw_err) {
*_stopSlewing = true; *_stopSlewing = true;
return mcc_deduce_error_code(hw_err, MccSimpleSlewingModelErrorCode::ERROR_HW_GETSTATE); return *_lastError = mcc_deduce_error_code(hw_err, MccSimpleSlewingModelErrorCode::ERROR_HW_GETSTATE);
} }
hw_state.X = (double)tdata.target.X; hw_state.X = (double)tdata.target.X;
hw_state.Y = (double)tdata.target.Y; hw_state.Y = (double)tdata.target.Y;
// hw_state.endptX = (double)tdata.target.X;
// hw_state.endptY = (double)tdata.target.Y;
{ {
std::lock_guard lock{*_currentParamsMutex}; std::lock_guard lock{*_currentParamsMutex};
hw_state.speedX = _currentParams.slewRateX; hw_state.speedX = _currentParams.slewRateX;
hw_state.speedY = _currentParams.slewRateY; hw_state.speedY = _currentParams.slewRateY;
min_time_to_pzone_in_secs =
std::chrono::duration_cast<std::chrono::duration<double>>(_currentParams.minTimeToPZone).count();
} }
hw_state.moving_state = CONTROLS_T::hardware_moving_state_t::HW_MOVE_SLEWING; hw_state.moving_state = CONTROLS_T::hardware_moving_state_t::HW_MOVE_SLEWING;
if (*_stopSlewing) { if (*_stopSlewing) {
logger.logDebug("slewing was stopped!"); logger.logDebug("slewing was stopped!");
return MccSimpleSlewingModelErrorCode::ERROR_STOPPED; return *_lastError = MccSimpleSlewingModelErrorCode::ERROR_STOPPED;
} }
// start slewing // start slewing
logger.logDebug(std::format("Send to hardware: X = {} degs, Y = {} degs", logger.logDebug(std::format("Send to hardware: X = {} degs, Y = {} degs",
mcc::MccAngle{hw_state.X}.degrees(), mcc::MccAngle{hw_state.Y}.degrees())); mcc::MccAngle{hw_state.X}.degrees(), mcc::MccAngle{hw_state.Y}.degrees()));
auto start_point = tdata.time_point; // needed for trajectory file
if constexpr (mccIsEquatorialMount(CONTROLS_T::mountType)) { if constexpr (mccIsEquatorialMount(CONTROLS_T::mountType)) {
logger.logDebug(std::format(" entered target: HA = {}, DEC = {}", logger.logDebug(std::format(" entered target: HA = {}, DEC = {}",
mcc::MccAngle{tdata.target.HA}.sexagesimal(true), mcc::MccAngle{tdata.target.HA}.sexagesimal(true),
@@ -276,6 +326,13 @@ public:
logger.logDebug(std::format(" current mount: HA = {}, DEC = {}", logger.logDebug(std::format(" current mount: HA = {}, DEC = {}",
mcc::MccAngle{tdata.HA}.sexagesimal(true), mcc::MccAngle{tdata.HA}.sexagesimal(true),
mcc::MccAngle{tdata.DEC_APP}.sexagesimal())); mcc::MccAngle{tdata.DEC_APP}.sexagesimal()));
if (store_path) {
fst << std::chrono::duration_cast<path_tp_t>(tdata.time_point - start_point).count() << " "
<< tdata.target.HA << " " << tdata.target.DEC_APP << " " << tdata.HA << " " << tdata.DEC_APP
<< " " << (tdata.target.HA - tdata.HA) << " " << (tdata.target.DEC_APP - tdata.DEC_APP) << " "
<< (int)hw_state.moving_state << "\n";
}
} else if constexpr (mccIsAltAzMount(CONTROLS_T::mountType)) { } else if constexpr (mccIsAltAzMount(CONTROLS_T::mountType)) {
logger.logDebug(std::format(" entered target: AZ = {}, ZD = {}", logger.logDebug(std::format(" entered target: AZ = {}, ZD = {}",
mcc::MccAngle{tdata.target.AZ}.sexagesimal(), mcc::MccAngle{tdata.target.AZ}.sexagesimal(),
@@ -288,27 +345,27 @@ public:
hw_err = controls->hardwareSetState(hw_state); hw_err = controls->hardwareSetState(hw_state);
if (hw_err) { if (hw_err) {
*_stopSlewing = true; *_stopSlewing = true;
return mcc_deduce_error_code(hw_err, MccSimpleSlewingModelErrorCode::ERROR_HW_SETSTATE); return *_lastError = mcc_deduce_error_code(hw_err, MccSimpleSlewingModelErrorCode::ERROR_HW_SETSTATE);
} }
logger.logDebug(" the 'hardwareSetState' method performed successfully!"); logger.logDebug(" the 'hardwareSetState' method performed successfully!");
// std::chrono::steady_clock::time_point start_slewing_tp, last_adjust_tp; // double dist, dx, dy;
// mcc_tp2tp(hw_state.time_point, start_slewing_tp); // not compiled!!
// double dist, dx, dy, sinY, rate2, xrate;
// std::chrono::duration<double> dtx, dty; // seconds in double
double dist; double dist;
// bool adjust_mode = false;
// static constexpr auto sideral_rate2 = slewing_params_t::sideralRate * slewing_params_t::sideralRate;
std::chrono::steady_clock::time_point start_slewing_tp, last_adjust_tp; std::chrono::steady_clock::time_point start_slewing_tp, last_adjust_tp;
start_slewing_tp = std::chrono::steady_clock::now(); start_slewing_tp = std::chrono::steady_clock::now();
last_adjust_tp = start_slewing_tp; last_adjust_tp = start_slewing_tp;
std::pair<double, double> distXY; std::pair<double, double> distXY;
bool tag_var_coord = true;
if (tdata.target.pair_kind == MccCoordPairKind::COORDS_KIND_AZALT ||
tdata.target.pair_kind == MccCoordPairKind::COORDS_KIND_AZZD) {
tag_var_coord = false;
}
// main loop (simply monitors the current position taking into account the prohibited zones, as well as the // main loop (simply monitors the current position taking into account the prohibited zones, as well as the
// timeout of the entire process) // timeout of the entire process)
@@ -320,13 +377,17 @@ public:
t_err = controls->waitForTelemetryData(&tdata, _currentParams.telemetryTimeout); t_err = controls->waitForTelemetryData(&tdata, _currentParams.telemetryTimeout);
if (t_err) { if (t_err) {
*_stopSlewing = true; *_lastError = mcc_deduce_error_code(t_err, MccSimpleSlewingModelErrorCode::ERROR_GET_TELEMETRY);
return mcc_deduce_error_code(t_err, MccSimpleSlewingModelErrorCode::ERROR_GET_TELEMETRY); break;
// *_stopSlewing = true;
// return mcc_deduce_error_code(t_err, MccSimpleSlewingModelErrorCode::ERROR_GET_TELEMETRY);
} }
} }
if (*_stopSlewing) { if (*_stopSlewing) {
return MccSimpleSlewingModelErrorCode::ERROR_STOPPED; *_lastError = MccSimpleSlewingModelErrorCode::ERROR_STOPPED;
break;
// return MccSimpleSlewingModelErrorCode::ERROR_STOPPED;
} }
distXY = mcc_compute_distance(tdata, min_time_to_pzone_in_secs, braking_accelX, braking_accelY); distXY = mcc_compute_distance(tdata, min_time_to_pzone_in_secs, braking_accelX, braking_accelY);
@@ -381,15 +442,41 @@ public:
mcc::MccAngleFancyString(tdata.speedX), mcc::MccAngleFancyString(tdata.speedX),
mcc::MccAngleFancyString(tdata.speedY))); mcc::MccAngleFancyString(tdata.speedY)));
pz_err = controls->inPZone(cpt, &in_zone); in_zone_vec.clear();
auto pz_err = controls->inPZone(cpt, &in_zone, &in_zone_vec);
if (pz_err) { if (pz_err) {
*_stopSlewing = true; *_lastError =
return mcc_deduce_error_code(pz_err, MccSimpleSlewingModelErrorCode::ERROR_PZONE_CONTAINER_COMP); mcc_deduce_error_code(pz_err, MccSimpleSlewingModelErrorCode::ERROR_PZONE_CONTAINER_COMP);
break;
// *_stopSlewing = true;
// return mcc_deduce_error_code(pz_err, MccSimpleSlewingModelErrorCode::ERROR_PZONE_CONTAINER_COMP);
} }
if (in_zone) { if (in_zone) {
*_stopSlewing = true; size_t i = 0;
return MccSimpleSlewingModelErrorCode::ERROR_NEAR_PZONE; for (; i < in_zone_vec.size(); ++i) {
if (in_zone_vec[i]) {
break;
}
}
logger.logError(
"target point is near prohibited zone (zone index: {})! Entered target coordinates:", i);
logger.logError(std::format(
" RA-APP, DEC-APP, HA, LST: {}, {}, {}, {}", mcc::MccAngle{tdata.RA_APP}.sexagesimal(true),
mcc::MccAngle{tdata.DEC_APP}.sexagesimal(), mcc::MccAngle{tdata.HA}.sexagesimal(true),
mcc::MccAngle{tdata.LST}.sexagesimal(true)));
logger.logError(std::format(" AZ, ZD, ALT: {}, {}, {}", mcc::MccAngle{tdata.AZ}.sexagesimal(),
mcc::MccAngle{tdata.ZD}.sexagesimal(),
mcc::MccAngle{tdata.ALT}.sexagesimal()));
logger.logError(std::format(" hardware X, Y: {}, {}", mcc::MccAngle{tdata.X}.sexagesimal(),
mcc::MccAngle{tdata.Y}.sexagesimal()));
*_lastError = MccSimpleSlewingModelErrorCode::ERROR_NEAR_PZONE;
break;
// *_stopSlewing = true;
// return MccSimpleSlewingModelErrorCode::ERROR_NEAR_PZONE;
} }
@@ -399,56 +486,148 @@ public:
if ((std::chrono::steady_clock::now() - start_slewing_tp) > _currentParams.slewTimeout) { if ((std::chrono::steady_clock::now() - start_slewing_tp) > _currentParams.slewTimeout) {
logger.logError("slewing process timeout!"); logger.logError("slewing process timeout!");
return MccSimpleSlewingModelErrorCode::ERROR_TIMEOUT; *_lastError = MccSimpleSlewingModelErrorCode::ERROR_TIMEOUT;
break;
// return MccSimpleSlewingModelErrorCode::ERROR_TIMEOUT;
} }
} }
logger.logTrace(std::format("get hw state ..."));
hw_err = controls->hardwareGetState(&hw_state); hw_err = controls->hardwareGetState(&hw_state);
if (hw_err) { if (hw_err) {
*_stopSlewing = true; *_lastError = mcc_deduce_error_code(hw_err, MccSimpleSlewingModelErrorCode::ERROR_HW_GETSTATE);
return mcc_deduce_error_code(hw_err, MccSimpleSlewingModelErrorCode::ERROR_HW_GETSTATE); break;
// *_stopSlewing = true;
// return mcc_deduce_error_code(hw_err, MccSimpleSlewingModelErrorCode::ERROR_HW_GETSTATE);
} }
if (slew_and_stop) { // just wait for mount to be stopped logger.logTrace(std::format("hw state was updated ({}, {} state = <{}>)",
MccAngle(hw_state.X).sexagesimal(true), MccAngle(hw_state.Y).sexagesimal(),
(int)hw_state.moving_state));
if (store_path) {
fst << std::chrono::duration_cast<path_tp_t>(tdata.time_point - start_point).count() << " "
<< tdata.target.HA << " " << tdata.target.DEC_APP << " " << tdata.HA << " " << tdata.DEC_APP
<< " " << (tdata.target.HA - tdata.HA) << " " << (tdata.target.DEC_APP - tdata.DEC_APP) << " "
<< (int)hw_state.moving_state << "\n";
}
if (slew_and_stop && !tag_var_coord) { // just wait for mount to be stopped
if (hw_state.moving_state == CONTROLS_T::hardware_moving_state_t::HW_MOVE_STOPPED) { if (hw_state.moving_state == CONTROLS_T::hardware_moving_state_t::HW_MOVE_STOPPED) {
logger.logInfo("mount moving state is STOPPED - exit!"); logger.logInfo("mount moving state is STOPPED - exit!");
break; break;
} }
} else { } else {
if (last_hw_time == tdata.time_point) {
logger.logTrace("Same hardware timepoint! Just continue to polling!\n\n\n\n");
continue;
}
last_hw_time = tdata.time_point;
t_err = controls->targetToMountDist(&dist); t_err = controls->targetToMountDist(&dist);
if (t_err) { if (t_err) {
*_stopSlewing = true; *_lastError =
return mcc_deduce_error_code(t_err, MccSimpleSlewingModelErrorCode::ERROR_DIST_TELEMETRY); mcc_deduce_error_code(t_err, MccSimpleSlewingModelErrorCode::ERROR_DIST_TELEMETRY);
break;
// *_stopSlewing = true;
// return mcc_deduce_error_code(t_err, MccSimpleSlewingModelErrorCode::ERROR_DIST_TELEMETRY);
} }
logger.logTrace(std::format(" target-to-mount distance: {}", mcc::MccAngleFancyString(dist))); logger.logTrace(std::format(" target-to-mount distance: {}", mcc::MccAngleFancyString(dist)));
if (dist <= _currentParams.slewToleranceRadius) { // stop slewing and exit from cycle if ((dist <= _currentParams.slewToleranceRadius) &&
(hw_state.moving_state ==
CONTROLS_T::hardware_moving_state_t::HW_MOVE_GUIDING)) { // stop slewing and exit from
// cycle
logger.logInfo("target-to-mount distance is lesser than slew tolerance radius - exit!"); logger.logInfo("target-to-mount distance is lesser than slew tolerance radius - exit!");
if (slew_and_stop) {
controls->hardwareStop();
}
break; break;
} }
if (*_stopSlewing) {
*_lastError = MccSimpleSlewingModelErrorCode::ERROR_STOPPED;
break;
// return MccSimpleSlewingModelErrorCode::ERROR_STOPPED;
}
// resend new position since target coordinates are changed in time // resend new position since target coordinates are changed in time
hw_state.X = (double)tdata.target.X; hw_state.X = (double)tdata.target.X;
hw_state.Y = (double)tdata.target.Y; hw_state.Y = (double)tdata.target.Y;
logger.logTrace(std::format("Send to hardware: X = {} degs, Y = {} degs", // controls->targetToMountDiff(tdata.pair_kind, &dx, &dy);
mcc::MccAngle{hw_state.X}.degrees(),
// // hw_state.endptX = hw_state.X + std::copysign(1.0_degs, dx);
// // hw_state.endptY = hw_state.Y + std::copysign(1.0_degs, dy);
// hw_state.endptX = hw_state.X + std::copysign(10.0_degs, dx);
// hw_state.endptY = hw_state.Y + std::copysign(10.0_degs, dy);
// logger.logTrace(std::format(
// "Send to hardware: {}, {}, tag: {}, {} (X = {} degs, Y = {} degs)",
// MccAngle(hw_state.X).sexagesimal(true), MccAngle(hw_state.Y).sexagesimal(),
// MccAngle(hw_state.endptX).sexagesimal(true), MccAngle(hw_state.endptY).sexagesimal(),
// mcc::MccAngle{hw_state.X}.degrees(), mcc::MccAngle{hw_state.Y}.degrees()));
logger.logTrace(std::format("Send to hardware: {}, {}, (X = {} degs, Y = {} degs)",
MccAngle(hw_state.X).sexagesimal(true),
MccAngle(hw_state.Y).sexagesimal(), mcc::MccAngle{hw_state.X}.degrees(),
mcc::MccAngle{hw_state.Y}.degrees())); mcc::MccAngle{hw_state.Y}.degrees()));
// hw_state.time_point += std::chrono::milliseconds(50);
hw_err = controls->hardwareSetState(hw_state); hw_err = controls->hardwareSetState(hw_state);
if (hw_err) { if (hw_err) {
*_stopSlewing = true; *_lastError = MccSimpleSlewingModelErrorCode::ERROR_HW_SETSTATE;
return mcc_deduce_error_code(hw_err, MccSimpleSlewingModelErrorCode::ERROR_HW_SETSTATE); break;
// *_stopSlewing = true;
// return mcc_deduce_error_code(hw_err, MccSimpleSlewingModelErrorCode::ERROR_HW_SETSTATE);
} }
logger.logDebug(" the 'hardwareSetState' method performed successfully!"); logger.logDebug(" the 'hardwareSetState' method performed successfully!");
// FOR DEBUG PURPOSE!!!!
// std::this_thread::sleep_for(std::chrono::milliseconds(50));
logger.logTrace(std::format("get hw state right after hardwareSetState ..."));
hw_err = controls->hardwareGetState(&hw_state);
if (hw_err) {
*_lastError = MccSimpleSlewingModelErrorCode::ERROR_HW_GETSTATE;
break;
// *_stopSlewing = true;
// return mcc_deduce_error_code(hw_err, MccSimpleSlewingModelErrorCode::ERROR_HW_GETSTATE);
}
logger.logTrace(std::format("hw state was updated ({}, {})", MccAngle(hw_state.X).sexagesimal(true),
MccAngle(hw_state.Y).sexagesimal()));
} }
if (*_stopSlewing) {
*_lastError = MccSimpleSlewingModelErrorCode::ERROR_STOPPED;
break;
// return MccSimpleSlewingModelErrorCode::ERROR_STOPPED;
}
// sleep here
std::this_thread::sleep_for(_currentParams.slewingTelemetryInterval);
} }
*_stopSlewing = true; *_stopSlewing = true;
logger.logInfo("Slewing finished"); logger.logInfo("Slewing finished");
error_t err = *_lastError;
logger.logInfo(std::format(" exit code: {} {} {}", err.value(), err.category().name(), err.message()));
// get final position
// wait for updated telemetry data // wait for updated telemetry data
{ {
@@ -457,11 +636,16 @@ public:
t_err = controls->waitForTelemetryData(&tdata, _currentParams.telemetryTimeout); t_err = controls->waitForTelemetryData(&tdata, _currentParams.telemetryTimeout);
if (t_err) { if (t_err) {
*_stopSlewing = true; return *_lastError =
return mcc_deduce_error_code(t_err, MccSimpleSlewingModelErrorCode::ERROR_GET_TELEMETRY); mcc_deduce_error_code(t_err, MccSimpleSlewingModelErrorCode::ERROR_GET_TELEMETRY);
} }
} }
t_err = controls->targetToMountDist(&dist);
if (t_err) {
return *_lastError = mcc_deduce_error_code(t_err, MccSimpleSlewingModelErrorCode::ERROR_DIST_TELEMETRY);
}
if constexpr (mccIsEquatorialMount(CONTROLS_T::mountType)) { if constexpr (mccIsEquatorialMount(CONTROLS_T::mountType)) {
logger.logDebug(std::format(" entered target: HA = {}, DEC = {}", logger.logDebug(std::format(" entered target: HA = {}, DEC = {}",
mcc::MccAngle{tdata.target.HA}.sexagesimal(true), mcc::MccAngle{tdata.target.HA}.sexagesimal(true),
@@ -469,6 +653,13 @@ public:
logger.logDebug(std::format(" current mount: HA = {}, DEC = {}", logger.logDebug(std::format(" current mount: HA = {}, DEC = {}",
mcc::MccAngle{tdata.HA}.sexagesimal(true), mcc::MccAngle{tdata.HA}.sexagesimal(true),
mcc::MccAngle{tdata.DEC_APP}.sexagesimal())); mcc::MccAngle{tdata.DEC_APP}.sexagesimal()));
if (store_path) {
fst << std::chrono::duration_cast<path_tp_t>(tdata.time_point - start_point).count() << " "
<< tdata.target.HA << " " << tdata.target.DEC_APP << " " << tdata.HA << " " << tdata.DEC_APP
<< " " << (tdata.target.HA - tdata.HA) << " " << (tdata.target.DEC_APP - tdata.DEC_APP) << " "
<< (int)hw_state.moving_state << "\n";
}
} else if constexpr (mccIsAltAzMount(CONTROLS_T::mountType)) { } else if constexpr (mccIsAltAzMount(CONTROLS_T::mountType)) {
logger.logDebug(std::format(" entered target: AZ = {}, ZD = {}", logger.logDebug(std::format(" entered target: AZ = {}, ZD = {}",
mcc::MccAngle{tdata.target.AZ}.sexagesimal(), mcc::MccAngle{tdata.target.AZ}.sexagesimal(),
@@ -478,7 +669,13 @@ public:
mcc::MccAngle{tdata.ZD}.sexagesimal())); mcc::MccAngle{tdata.ZD}.sexagesimal()));
} }
return MccSimpleSlewingModelErrorCode::ERROR_OK; logger.logDebug(std::format(" target-to-mount distance {}", mcc::MccAngleFancyString(dist)));
if (store_path) {
fst.close();
}
return *_lastError = MccSimpleSlewingModelErrorCode::ERROR_OK;
}; };
} }
@@ -500,6 +697,17 @@ public:
*_stopSlewing = false; *_stopSlewing = false;
// check for target in p-zone
*_lastError = _checkTargetFunc();
if (_lastError->load()) { // return here immediately
return *_lastError;
}
// // asynchronous slewing process
// _slewFuncFuture = std::async(std::launch::async, _slewingFunc, slew_and_stop);
// return MccSimpleSlewingModelErrorCode::ERROR_OK;
return _slewingFunc(slew_and_stop); return _slewingFunc(slew_and_stop);
} }
@@ -533,6 +741,11 @@ public:
return _currentParams; return _currentParams;
} }
error_t slewingLastError() const
{
return *_lastError;
}
protected: protected:
std::function<error_t(bool)> _slewingFunc{}; std::function<error_t(bool)> _slewingFunc{};
std::unique_ptr<std::atomic_bool> _stopSlewing; std::unique_ptr<std::atomic_bool> _stopSlewing;
@@ -540,6 +753,9 @@ protected:
slewing_params_t _currentParams{}; slewing_params_t _currentParams{};
std::unique_ptr<std::mutex> _currentParamsMutex{}; std::unique_ptr<std::mutex> _currentParamsMutex{};
std::unique_ptr<std::atomic<error_t>> _lastError;
std::future<error_t> _slewFuncFuture{};
}; };

View File

@@ -9,7 +9,7 @@
#include <condition_variable> #include <condition_variable>
#include <future> #include <future>
#include <mutex> #include <mutex>
#include <thread> #include <stop_token>
#include "mcc_defaults.h" #include "mcc_defaults.h"
@@ -24,7 +24,8 @@ enum class MccTelemetryErrorCode : int {
ERROR_HARDWARE_GETPOS, ERROR_HARDWARE_GETPOS,
ERROR_UPDATE_STOPPED, ERROR_UPDATE_STOPPED,
ERROR_DATA_TIMEOUT, ERROR_DATA_TIMEOUT,
ERROR_UNSUPPORTED_COORD_PAIR ERROR_UNSUPPORTED_COORD_PAIR,
ERROR_UPDATE_LOOP_WAIT
}; };
} // namespace mcc } // namespace mcc
@@ -75,6 +76,8 @@ struct MccTelemetryCategory : public std::error_category {
return "a timeout occured while waiting for new data"; return "a timeout occured while waiting for new data";
case MccTelemetryErrorCode::ERROR_UNSUPPORTED_COORD_PAIR: case MccTelemetryErrorCode::ERROR_UNSUPPORTED_COORD_PAIR:
return "unsupported coordinate pair"; return "unsupported coordinate pair";
case MccTelemetryErrorCode::ERROR_UPDATE_LOOP_WAIT:
return "a timeout occured while waiting to exit the update loop";
default: default:
return "UNKNOWN"; return "UNKNOWN";
} }
@@ -95,6 +98,293 @@ inline std::error_code make_error_code(MccTelemetryErrorCode ec)
template <mcc_hardware_c HARDWARE_T>
class MccMountTelemetry : public mcc_telemetry_interface_t<std::error_code>
{
public:
static constexpr std::chrono::milliseconds defaultDataUpdatingTimeout{100};
typedef std::error_code error_t;
struct telemetry_data_t : MccEqtHrzCoords {
MccCelestialPoint entered_target{};
MccEqtHrzCoords target;
HARDWARE_T::hardware_state_t hardware;
double JD;
double LST;
double EO;
double refCorr;
};
template <mcc_PCM_c PCM_T, mcc_ccte_c CCTE_T>
MccMountTelemetry(HARDWARE_T* hardware, PCM_T* pcm, CCTE_T* ccte)
{
_data.entered_target.pair_kind = MccCoordPairKind::COORDS_KIND_RADEC_ICRS;
if constexpr (mccIsEquatorialMount(PCM_T::mountType)) {
_data.pair_kind = MccCoordPairKind::COORDS_KIND_HADEC_APP;
_data.target.pair_kind = MccCoordPairKind::COORDS_KIND_HADEC_APP;
} else if constexpr (mccIsAltAzMount(PCM_T::mountType)) {
_data.pair_kind = MccCoordPairKind::COORDS_KIND_AZZD;
_data.target.pair_kind = MccCoordPairKind::COORDS_KIND_AZZD;
} else {
static_assert(false, "UNKNOWN MOUNT TYPE!");
}
// target coordinates updater
_updateTargetFunc = [hardware, pcm, ccte, this](std::stop_token stop_token) -> error_t {
MccPCMResult pcm_res;
mcc_tp2tp(_data.time_point, _data.target.time_point);
mcc_tp2tp(_data.time_point, _data.entered_target.time_point);
if (_data.entered_target.pair_kind ==
MccCoordPairKind::COORDS_KIND_XY) { // compute corresponded observed coordinates
auto pcm_err = pcm->computePCM(_data.entered_target, &pcm_res, &_data.target);
if (pcm_err) {
return mcc_deduce_error_code(pcm_err, MccTelemetryErrorCode::ERROR_PCM_COMP);
}
_data.target.X = _data.entered_target.X;
_data.target.Y = _data.entered_target.Y;
if (stop_token.stop_requested()) {
return MccTelemetryErrorCode::ERROR_UPDATE_STOPPED;
}
if constexpr (mccIsEquatorialMount(PCM_T::mountType)) {
_data.target.pair_kind = MccCoordPairKind::COORDS_KIND_HADEC_APP;
} else if constexpr (mccIsAltAzMount(PCM_T::mountType)) {
_data.target.pair_kind = MccCoordPairKind::COORDS_KIND_AZZD;
} else {
static_assert(false, "UNKNOWN MOUNT TYPE!!!");
}
auto ccte_err = ccte->transformCoordinates(_data.target, &_data.target);
if (ccte_err) {
return mcc_deduce_error_code(ccte_err, MccTelemetryErrorCode::ERROR_COORD_TRANSFORM);
}
} else {
_data.target.pair_kind = _data.entered_target.pair_kind;
auto ccte_err = ccte->transformCoordinates(_data.entered_target, &_data.target);
if (ccte_err) {
return mcc_deduce_error_code(ccte_err, MccTelemetryErrorCode::ERROR_COORD_TRANSFORM);
}
auto pcm_err = pcm->computeReversePCM(_data.target, &pcm_res, &_data.target);
if (pcm_err) {
return mcc_deduce_error_code(pcm_err, MccTelemetryErrorCode::ERROR_PCM_COMP);
}
}
return MccTelemetryErrorCode::ERROR_OK;
};
// mount current coordinates updater
_updateFunc = [hardware, pcm, ccte, this](std::stop_token stop_token) -> std::error_code {
// std::lock_guard lock{*_updateMutex};
// first, update mount quantities
auto hw_err = hardware->hardwareGetState(&_data.hardware);
if (hw_err) {
return mcc_deduce_error_code(hw_err, MccTelemetryErrorCode::ERROR_HARDWARE_GETPOS);
}
// if (stop_token.stop_requested()) {
// return MccTelemetryErrorCode::ERROR_UPDATE_STOPPED;
// }
double eo;
mcc_tp2tp(_data.hardware.time_point, _data.time_point);
_data.X = _data.hardware.X;
_data.Y = _data.hardware.Y;
auto ccte_err = _data->timepointToJulday(_data.time_point, &_data.JD);
if (!ccte_err) {
if (stop_token.stop_requested()) {
return MccTelemetryErrorCode::ERROR_UPDATE_STOPPED;
}
ccte_err = ccte_err->juldayToAppSideral(_data.JD, &_data.LST, true);
if (!ccte_err) {
ccte_err = ccte_err->equationOrigins(_data.JD, &eo);
_data.EO = eo;
}
}
if (ccte_err) {
return mcc_deduce_error_code(ccte_err, MccTelemetryErrorCode::ERROR_COORD_TRANSFORM);
}
// if (stop_token.stop_requested()) {
// return MccTelemetryErrorCode::ERROR_UPDATE_STOPPED;
// }
MccCelestialPoint pt;
mcc_tp2tp(_data.time_point, pt.time_point);
auto pcm_err = pcm->computePCM(_data, &_data, &pt);
if (pcm_err) {
return mcc_deduce_error_code(pcm_err, MccTelemetryErrorCode::ERROR_PCM_COMP);
}
if constexpr (mccIsEquatorialMount(PCM_T::mountType)) {
pt.pair_kind = MccCoordPairKind::COORDS_KIND_HADEC_APP;
_data.pair_kind = MccCoordPairKind::COORDS_KIND_HADEC_APP;
} else if constexpr (mccIsAltAzMount(PCM_T::mountType)) {
pt.pair_kind = MccCoordPairKind::COORDS_KIND_AZALT;
_data.pair_kind = MccCoordPairKind::COORDS_KIND_AZALT;
} else {
static_assert(false, "UNKNOWN MOUNT TYPE!");
}
ccte_err = ccte->transformCoordinates(pt, &_data);
if (!ccte_err) {
ccte_err = ccte->refractionCorrection(_data, &_data.refCorr);
if (!ccte_err) {
return _updateTargetFunc(stop_token);
} else {
return mcc_deduce_error_code(ccte_err, MccTelemetryErrorCode::ERROR_COORD_TRANSFORM);
}
} else {
return mcc_deduce_error_code(ccte_err, MccTelemetryErrorCode::ERROR_COORD_TRANSFORM);
}
return MccTelemetryErrorCode::ERROR_OK;
};
_internalUpdatingStopSource = std::stop_source{};
_dataUpdatingRequested->clear();
_dataUpdatingStart->clear();
// start thread for data updating
_updatingFuture = std::async(
std::launch::async,
[this](std::stop_token stoken) {
while (!stoken.stop_requested()) {
_dataUpdatingRequested->wait(false);
if (!stoken.stop_requested()) {
std::lock_guard lock{*_timeoutMutex};
_dataUpdatingStart->test_and_set();
_dataUpdatingStart->notify_all();
_lastUpdateError = _updateFunc(stoken);
_dataUpdatingStart->clear();
_dataUpdatingRequested->clear();
}
}
},
_internalUpdatingStopSource.get_token());
}
virtual ~MccMountTelemetry() = default;
void setDataUpdatingTimeout(traits::mcc_time_duration_c auto const& timeout)
{
_dataUpdatingTimeout = std::chrono::duration_cast<decltype(_dataUpdatingTimeout)>(timeout);
}
template <traits::mcc_time_duration_c DT>
DT getDataUpdatingTimeout() const
{
return std::chrono::duration_cast<DT>(_dataUpdatingTimeout);
}
std::chrono::milliseconds getDataUpdatingTimeout() const
{
return getDataUpdatingTimeout<std::chrono::milliseconds>();
}
error_t telemetryData(telemetry_data_t* tdata)
{
if (tdata == nullptr) {
return MccTelemetryErrorCode::ERROR_NULLPTR;
}
// trigger updating
_dataUpdatingRequested->test_and_set();
_dataUpdatingRequested->notify_one();
// wait for updating start
_dataUpdatingStart->wait(false);
if (_timeoutMutex->try_lock_for(_dataUpdatingTimeout)) {
_timeoutMutex->unlock();
} else {
_lastUpdateError = MccTelemetryErrorCode::ERROR_DATA_TIMEOUT;
}
if (!_lastUpdateError) {
std::lock_guard lock(*_updateMutex);
*tdata = _data;
}
return _lastUpdateError;
}
error_t setPointingTarget(mcc_celestial_point_c auto const& cp)
{
std::lock_guard lock(*_updateMutex);
mcc_copy_celestial_point(cp, &_data.entered_target);
return MccTelemetryErrorCode::ERROR_OK;
}
protected:
telemetry_data_t _data;
std ::function<error_t(std::stop_token)> _updateTargetFunc{};
std::function<error_t(std::stop_token)> _updateFunc{};
std::unique_ptr<std::mutex> _updateMutex;
std::unique_ptr<std::condition_variable> _updateCondVar;
std::stop_source _internalUpdatingStopSource{};
std::future<void> _updatingFuture{};
std::unique_ptr<std::atomic_flag> _dataUpdatingRequested{new std::atomic_flag{}};
std::unique_ptr<std::atomic_flag> _dataUpdatingStart{new std::atomic_flag{}};
std::unique_ptr<std::timed_mutex> _timeoutMutex{new std::timed_mutex()};
std::chrono::nanoseconds _dataUpdatingTimeout{defaultDataUpdatingTimeout};
error_t _lastUpdateError{MccTelemetryErrorCode::ERROR_OK};
void updateLoop(std::stop_token stoken)
{
while (!stoken.stop_requested()) {
_dataUpdatingRequested->wait(false);
if (!stoken.stop_requested()) {
std::lock_guard lock{*_timeoutMutex};
_dataUpdatingStart->test_and_set();
_dataUpdatingStart->notify_all();
_lastUpdateError = _updateFunc(stoken);
_dataUpdatingStart->clear();
_dataUpdatingRequested->clear();
}
}
}
};
class MccTelemetry : public mcc_telemetry_interface_t<std::error_code> class MccTelemetry : public mcc_telemetry_interface_t<std::error_code>
{ {
protected: protected:
@@ -104,6 +394,8 @@ public:
static constexpr auto defaultUpdateInterval = std::chrono::milliseconds(100); static constexpr auto defaultUpdateInterval = std::chrono::milliseconds(100);
static constexpr auto defaultInternalUpdateTimeout = defaultUpdateInterval * 5; static constexpr auto defaultInternalUpdateTimeout = defaultUpdateInterval * 5;
static constexpr auto defaultUpdatingTimeout = std::chrono::milliseconds(500);
typedef std::error_code error_t; typedef std::error_code error_t;
@@ -126,6 +418,8 @@ public:
using pcm_t = std::remove_cvref_t<decltype(*controls)>; using pcm_t = std::remove_cvref_t<decltype(*controls)>;
using hardware_t = std::remove_cvref_t<decltype(*controls)>; using hardware_t = std::remove_cvref_t<decltype(*controls)>;
_data.entered_target.pair_kind = MccCoordPairKind::COORDS_KIND_RADEC_ICRS;
if constexpr (mccIsEquatorialMount(pcm_t::mountType)) { if constexpr (mccIsEquatorialMount(pcm_t::mountType)) {
_data.pair_kind = MccCoordPairKind::COORDS_KIND_HADEC_APP; _data.pair_kind = MccCoordPairKind::COORDS_KIND_HADEC_APP;
_data.target.pair_kind = MccCoordPairKind::COORDS_KIND_HADEC_APP; _data.target.pair_kind = MccCoordPairKind::COORDS_KIND_HADEC_APP;
@@ -140,16 +434,11 @@ public:
MccPCMResult pcm_res; MccPCMResult pcm_res;
mcc_tp2tp(_data.time_point, _data.target.time_point); mcc_tp2tp(_data.time_point, _data.target.time_point);
mcc_tp2tp(_data.time_point, _data.entered_target.time_point);
bool hw_coords = _data.target.pair_kind == MccCoordPairKind::COORDS_KIND_XY; if (_data.entered_target.pair_kind ==
MccCelestialPoint hw_cp{.pair_kind = MccCoordPairKind::COORDS_KIND_XY}; MccCoordPairKind::COORDS_KIND_XY) { // compute corresponded observed coordinates
mcc_tp2tp(_data.time_point, hw_cp.time_point); auto pcm_err = controls->computePCM(_data.entered_target, &pcm_res, &_data.target);
if (hw_coords) { // compute corresponded apparent coordinates
hw_cp.X = _data.target.X;
hw_cp.Y = _data.target.Y;
auto pcm_err = controls->computePCM(_data.target, &pcm_res, &_data.target);
if (pcm_err) { if (pcm_err) {
return mcc_deduce_error_code(pcm_err, MccTelemetryErrorCode::ERROR_PCM_COMP); return mcc_deduce_error_code(pcm_err, MccTelemetryErrorCode::ERROR_PCM_COMP);
} }
@@ -165,71 +454,35 @@ public:
} else { } else {
static_assert(false, "UNKNOWN MOUNT TYPE!!!"); static_assert(false, "UNKNOWN MOUNT TYPE!!!");
} }
}
if (_data.target.pair_kind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS) { auto ccte_err = controls->transformCoordinates(_data.target, &_data.target);
_data.target.X = _data.target.RA_ICRS; if (ccte_err) {
_data.target.Y = _data.target.DEC_ICRS; return mcc_deduce_error_code(ccte_err, MccTelemetryErrorCode::ERROR_COORD_TRANSFORM);
} else if (_data.target.pair_kind == MccCoordPairKind::COORDS_KIND_AZZD) { }
_data.target.X = _data.target.AZ;
_data.target.Y = _data.target.ZD;
} else if (_data.target.pair_kind == MccCoordPairKind::COORDS_KIND_AZALT) {
_data.target.X = _data.target.AZ;
_data.target.Y = _data.target.ALT;
} else if (_data.target.pair_kind == MccCoordPairKind::COORDS_KIND_HADEC_APP) {
_data.target.X = _data.target.HA;
_data.target.Y = _data.target.DEC_APP;
} else if (_data.target.pair_kind == MccCoordPairKind::COORDS_KIND_RADEC_APP) {
_data.target.X = _data.target.RA_APP;
_data.target.Y = _data.target.DEC_APP;
} else { } else {
return MccTelemetryErrorCode::ERROR_UNSUPPORTED_COORD_PAIR; _data.target.pair_kind = _data.entered_target.pair_kind;
}
auto ccte_err = controls->transformCoordinates(_data.target, &_data.target); auto ccte_err = controls->transformCoordinates(_data.entered_target, &_data.target);
if (ccte_err) { if (ccte_err) {
if (hw_coords) { // restore coordinates pair kind return mcc_deduce_error_code(ccte_err, MccTelemetryErrorCode::ERROR_COORD_TRANSFORM);
_data.target.pair_kind = MccCoordPairKind::COORDS_KIND_XY;
} }
return mcc_deduce_error_code(ccte_err, MccTelemetryErrorCode::ERROR_COORD_TRANSFORM); // if (_data.entered_target.pair_kind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS) {
// _data.target.RA_ICRS = _data.entered_target.X;
// _data.target.DEC_ICRS = _data.entered_target.Y;
// }
} }
return MccTelemetryErrorCode::ERROR_OK;
if (stop_token.stop_requested()) { if (stop_token.stop_requested()) {
return MccTelemetryErrorCode::ERROR_UPDATE_STOPPED; return MccTelemetryErrorCode::ERROR_UPDATE_STOPPED;
} }
if (_data.target.pair_kind != MccCoordPairKind::COORDS_KIND_RADEC_ICRS) { if (_data.target.pair_kind != MccCoordPairKind::COORDS_KIND_RADEC_ICRS) {
// fixed apparent coordinates (AZZD or HADEC)
// needs to compute ICRS
// (.X and .Y are already assigned above!)
// if (_data.target.pair_kind == MccCoordPairKind::COORDS_KIND_AZZD) {
// _data.target.X = _data.target.AZ;
// _data.target.Y = _data.target.ZD;
// } else if (_data.target.pair_kind == MccCoordPairKind::COORDS_KIND_AZALT) {
// _data.target.X = _data.target.AZ;
// _data.target.Y = _data.target.ALT;
// } else if (_data.target.pair_kind == MccCoordPairKind::COORDS_KIND_HADEC_APP) {
// _data.target.X = _data.target.HA;
// _data.target.Y = _data.target.DEC_APP;
// } else if (_data.target.pair_kind == MccCoordPairKind::COORDS_KIND_RADEC_APP) {
// _data.target.X = _data.target.RA_APP;
// _data.target.Y = _data.target.DEC_APP;
// } else {
// return MccTelemetryErrorCode::ERROR_UNSUPPORTED_COORD_PAIR;
// }
MccCelestialPoint pt{.pair_kind = MccCoordPairKind::COORDS_KIND_RADEC_ICRS}; MccCelestialPoint pt{.pair_kind = MccCoordPairKind::COORDS_KIND_RADEC_ICRS};
ccte_err = controls->transformCoordinates(_data.target, &pt); auto ccte_err = controls->transformCoordinates(_data.target, &pt);
if (ccte_err) { if (ccte_err) {
if (hw_coords) { // restore coordinates pair kind
_data.target.pair_kind = MccCoordPairKind::COORDS_KIND_XY;
_data.target.X = hw_cp.X;
_data.target.Y = hw_cp.Y;
}
return mcc_deduce_error_code(ccte_err, MccTelemetryErrorCode::ERROR_COORD_TRANSFORM); return mcc_deduce_error_code(ccte_err, MccTelemetryErrorCode::ERROR_COORD_TRANSFORM);
} }
_data.target.RA_ICRS = pt.X; _data.target.RA_ICRS = pt.X;
@@ -241,21 +494,19 @@ public:
} }
// hardware coordinates // hardware coordinates
if (!hw_coords) { if (_data.entered_target.pair_kind != MccCoordPairKind::COORDS_KIND_XY) {
auto pcm_err = controls->computeInversePCM(_data.target, &pcm_res, &_data.target); auto pcm_err = controls->computeInversePCM(_data.target, &pcm_res, &_data.target);
if (pcm_err) { if (pcm_err) {
return mcc_deduce_error_code(pcm_err, MccTelemetryErrorCode::ERROR_PCM_COMP); return mcc_deduce_error_code(pcm_err, MccTelemetryErrorCode::ERROR_PCM_COMP);
} }
} else { // restore coordinates pair kind
_data.target.pair_kind = MccCoordPairKind::COORDS_KIND_XY;
_data.target.X = hw_cp.X;
_data.target.Y = hw_cp.Y;
} }
return MccTelemetryErrorCode::ERROR_OK; return MccTelemetryErrorCode::ERROR_OK;
}; };
_updateFunc = [controls, this](std::stop_token stop_token) -> std::error_code { _updateFunc = [controls, this](std::stop_token stop_token) -> std::error_code {
// std::lock_guard lock{*_updateMutex};
// first, update mount quantities // first, update mount quantities
typename hardware_t::hardware_state_t hw_pos; typename hardware_t::hardware_state_t hw_pos;
auto hw_err = controls->hardwareGetState(&hw_pos); auto hw_err = controls->hardwareGetState(&hw_pos);
@@ -269,8 +520,7 @@ public:
double eo; double eo;
_data.time_point = mcc_tp2tp(hw_pos.time_point, hw_pos.time_point);
std::chrono::time_point_cast<typename decltype(_data.time_point)::duration>(hw_pos.time_point);
auto ccte_err = controls->timepointToJulday(_data.time_point, &_data.JD); auto ccte_err = controls->timepointToJulday(_data.time_point, &_data.JD);
if (!ccte_err) { if (!ccte_err) {
@@ -298,6 +548,40 @@ public:
_data.speedX = (double)hw_pos.speedX; _data.speedX = (double)hw_pos.speedX;
_data.speedY = (double)hw_pos.speedY; _data.speedY = (double)hw_pos.speedY;
// 2025.12.18: according to addition RA/DEC_ICRS to mcc_eqt_hrz_coord_c ...
MccCelestialPoint pt;
mcc_tp2tp(_data.time_point, pt.time_point);
auto pcm_err = controls->computePCM(_data, &_data, &pt);
if (pcm_err) {
return mcc_deduce_error_code(pcm_err, MccTelemetryErrorCode::ERROR_PCM_COMP);
}
if constexpr (mccIsEquatorialMount(pcm_t::mountType)) {
pt.pair_kind = MccCoordPairKind::COORDS_KIND_HADEC_APP;
_data.pair_kind = MccCoordPairKind::COORDS_KIND_HADEC_APP;
} else if constexpr (mccIsAltAzMount(pcm_t::mountType)) {
pt.pair_kind = MccCoordPairKind::COORDS_KIND_AZALT;
_data.pair_kind = MccCoordPairKind::COORDS_KIND_AZALT;
} else {
static_assert(false, "UNKNOWN MOUNT TYPE!");
}
ccte_err = controls->transformCoordinates(pt, &_data);
if (!ccte_err) {
ccte_err = controls->refractionCorrection(_data, &_data.refCorr);
if (!ccte_err) {
return _updateTargetFunc(stop_token);
} else {
return mcc_deduce_error_code(ccte_err, MccTelemetryErrorCode::ERROR_COORD_TRANSFORM);
}
} else {
return mcc_deduce_error_code(ccte_err, MccTelemetryErrorCode::ERROR_COORD_TRANSFORM);
}
/*
// fill _data.pcmX, _data.pcmY and corresponded apparent coordinates // fill _data.pcmX, _data.pcmY and corresponded apparent coordinates
auto pcm_err = controls->computePCM(_data, &_data, &_data); auto pcm_err = controls->computePCM(_data, &_data, &_data);
if (pcm_err) { if (pcm_err) {
@@ -311,9 +595,9 @@ public:
MccCelestialPoint pt{.pair_kind = MccCoordPairKind::COORDS_KIND_AZALT, .time_point = _data.time_point}; MccCelestialPoint pt{.pair_kind = MccCoordPairKind::COORDS_KIND_AZALT, .time_point = _data.time_point};
if constexpr (mccIsEquatorialMount(pcm_t::mountType)) { if constexpr (mccIsEquatorialMount(pcm_t::mountType)) {
// NOTE: now it are OBSERVED (NOT APPARENT) RA, HA, DEC!!!
_data.RA_APP = _data.RA_APP =
MccAngle((double)_data.LST - (double)_data.HA - eo).normalize<MccAngle::NORM_KIND_0_360>(); MccAngle((double)_data.LST - (double)_data.HA + eo).normalize<MccAngle::NORM_KIND_0_360>();
// MccAngle((double)_data.LST - (double)_data.HA + eo).normalize<MccAngle::NORM_KIND_0_360>();
_data.X = _data.HA; _data.X = _data.HA;
_data.Y = _data.DEC_APP; _data.Y = _data.DEC_APP;
@@ -340,8 +624,7 @@ public:
_data.HA = pt.X; _data.HA = pt.X;
_data.DEC_APP = pt.Y; _data.DEC_APP = pt.Y;
_data.RA_APP = _data.RA_APP =
MccAngle((double)_data.LST - (double)_data.HA - eo).normalize<MccAngle::NORM_KIND_0_360>(); MccAngle((double)_data.LST - (double)_data.HA + eo).normalize<MccAngle::NORM_KIND_0_360>();
// MccAngle((double)_data.LST - (double)_data.HA + eo).normalize<MccAngle::NORM_KIND_0_360>();
} }
} else { } else {
@@ -360,18 +643,32 @@ public:
ccte_err = controls->refractionCorrection(_data, &_data.refCorr); ccte_err = controls->refractionCorrection(_data, &_data.refCorr);
if (!ccte_err) { if (!ccte_err) {
// // compute APPARENT RA, HA and DEC from observer AZ, ZD
// _data.Y += _data.refCorr; // zenithal distance corrected for the refraction
// pt.pair_kind = MccCoordPairKind::COORDS_KIND_HADEC_APP;
// ccte_err = controls->transformCoordinates(_data, &pt);
// if (!ccte_err) {
// _data.HA = pt.X;
// _data.DEC_APP = pt.Y;
// _data.RA_APP =
// MccAngle((double)_data.LST - (double)_data.HA -
// eo).normalize<MccAngle::NORM_KIND_0_360>();
// restore hardware encoders coordinates // restore hardware encoders coordinates
_data.X = (double)hw_pos.X; _data.X = (double)hw_pos.X;
_data.Y = (double)hw_pos.Y; _data.Y = (double)hw_pos.Y;
// update target (assuming target ICRS coordinates are already set)
// auto ret = _updateTargetFunc(false, stop_token);
// update target according to its .pair_kind! // update target according to its .pair_kind!
auto ret = _updateTargetFunc(stop_token); auto ret = _updateTargetFunc(stop_token);
if (ret) { if (ret) {
return ret; return ret;
} }
// }
} }
} }
@@ -389,31 +686,21 @@ public:
return mcc_deduce_error_code(ccte_err, MccTelemetryErrorCode::ERROR_COORD_TRANSFORM); return mcc_deduce_error_code(ccte_err, MccTelemetryErrorCode::ERROR_COORD_TRANSFORM);
} }
*/
return MccTelemetryErrorCode::ERROR_OK; return MccTelemetryErrorCode::ERROR_OK;
}; };
// update thread // update thread
// _updatingFuture = std::async( _internalUpdatingStopSource = std::stop_source{};
// std::launch::async, *_internalUpdating = false;
// [this](std::stop_token stoken) {
// while (!stoken.stop_requested()) {
// {
// std::unique_lock ulock{*_updateMutex};
// bool ok = _updateCondVar->wait(ulock, [&stoken, this]() -> bool { // _dataUpdatingRequested->clear();
// return _dataUpdatingRequested || stoken.stop_requested(); // _dataUpdatingStart->clear();
// });
// }
// if (!stoken.stop_requested()) { // _updatingFuture =
// std::lock_guard lock{*_timeoutMutex}; // std::async(std::launch::async, &MccTelemetry::updateLoop, this, _internalUpdatingStopSource.get_token());
// _lastUpdateError = _updateFunc(stoken);
// }
// }
// },
// _internalUpdatingStopSource.get_token());
} }
@@ -428,11 +715,9 @@ public:
{ {
stopInternalTelemetryDataUpdating(); stopInternalTelemetryDataUpdating();
if (_internalUpdatingFuture.valid()) { if (_updatingFuture.valid()) {
// try to exit correctly // try to exit correctly
// auto status = _internalUpdatingFuture.wait_for(std::chrono::seconds(1)); _updatingFuture.wait_for(std::chrono::seconds(1));
_internalUpdatingFuture.wait_for(std::chrono::seconds(1));
// _internalUpdatingFuture.get();
} }
}; };
@@ -478,70 +763,69 @@ public:
// asynchronuosly periodicaly update telemetry data (internal synchronization) // asynchronuosly periodicaly update telemetry data (internal synchronization)
void startInternalTelemetryDataUpdating() void startInternalTelemetryDataUpdating()
{ {
*_internalUpdating = true;
_internalUpdatingStopSource = std::stop_source{};
return;
using intv_t = std::remove_cvref_t<decltype(_currentUpdateInterval)>; using intv_t = std::remove_cvref_t<decltype(_currentUpdateInterval)>;
_internalUpdatingStopSource = std::stop_source{}; // reset state if (_internalUpdatingStopSource.stop_requested()) {
_internalUpdatingStopSource = std::stop_source{}; // reset stop source
if (_updatingFuture.valid()) { // it should not be!!!
// updating loop was stopped, just wait fo future result
// it must return immediately!
auto status = _updatingFuture.wait_for(std::chrono::milliseconds(500));
if (status == std::future_status::ready) { // OK!
*_internalUpdating = true; } else if (status == std::future_status::deferred) { // ???!!!!
_updatingFuture.get();
_internalUpdatingFuture = std::async( } else {
std::launch::async, _lastUpdateError = MccTelemetryErrorCode::ERROR_UPDATE_LOOP_WAIT;
[this](std::stop_token stop_token) -> error_t {
while (!stop_token.stop_requested()) {
// while (true) {
_lastUpdateError = updateTelemetryData(_currentUpdateTimeout);
if (_lastUpdateError) {
*_internalUpdating = false;
return _lastUpdateError;
}
// auto nn = std::this_thread::get_id();
std::this_thread::sleep_for(_currentUpdateInterval);
// {
// std::lock_guard lock{*_currentUpdateIntervalMutex};
// // compute it here because of possible changing _currentUpdateInterval
// auto sleep_td = _currentUpdateInterval / internalUpdatingIntervalDiv;
// for (uint16_t i = 0; i < internalUpdatingIntervalDiv - 1; ++i) {
// if (stop_token.stop_requested()) {
// break;
// }
// std::this_thread::sleep_for(sleep_td);
// }
// if (stop_token.stop_requested()) {
// break;
// }
// if constexpr (std::floating_point<intv_t>) {
// std::this_thread::sleep_for(sleep_td);
// } else {
// auto rem = _currentUpdateInterval % internalUpdatingIntervalDiv;
// if (rem.count()) {
// std::this_thread::sleep_for(rem);
// } else {
// std::this_thread::sleep_for(sleep_td);
// }
// }
// }
} }
*_internalUpdating = false; _updatingFuture = std::async(std::launch::async, &MccTelemetry::updateLoop, this,
return MccTelemetryErrorCode::ERROR_OK; _internalUpdatingStopSource.get_token());
},
_internalUpdatingStopSource.get_token());
*_internalUpdating = true;
} else {
_updatingFuture = std::async(std::launch::async, &MccTelemetry::updateLoop, this,
_internalUpdatingStopSource.get_token());
*_internalUpdating = true;
}
} // here the loop is already started
} }
void stopInternalTelemetryDataUpdating() void stopInternalTelemetryDataUpdating()
{ {
// reset all possible locks
_internalUpdatingStopSource.request_stop(); _internalUpdatingStopSource.request_stop();
*_internalUpdating = false; *_internalUpdating = false;
return;
_dataUpdatingRequested->test_and_set();
_dataUpdatingRequested->notify_one();
_dataUpdatingStart->test_and_set();
_dataUpdatingStart->notify_all();
auto status = _updatingFuture.wait_for(std::chrono::milliseconds(500));
if (status == std::future_status::ready) { // OK!
} else if (status == std::future_status::deferred) { // ???!!!!
_updatingFuture.get();
} else {
_lastUpdateError = MccTelemetryErrorCode::ERROR_UPDATE_LOOP_WAIT;
}
_dataUpdatingRequested->clear();
_dataUpdatingStart->clear();
*_internalUpdating = false;
} }
@@ -553,64 +837,59 @@ public:
error_t updateTelemetryData(traits::mcc_time_duration_c auto const& timeout) error_t updateTelemetryData(traits::mcc_time_duration_c auto const& timeout)
{ {
{ std::lock_guard thread_lock{*_updateMutex};
std::lock_guard thread_lock{*_updateMutex};
std::stop_source stop_source; // _internalUpdatingStopSource = std::stop_source{};
// return _lastUpdateError = _updateFunc(_internalUpdatingStopSource.get_token());
*_isDataUpdated = false;
// std::future<error_t> update_ft = std::async(std::launch::async, _updateFunc, stop_source.get_token()); auto ft = std::async(std::launch::async, _updateFunc, _internalUpdatingStopSource.get_token());
// // std::future<error_t> update_ft = auto st = ft.wait_for(timeout);
// // std::async(std::launch::async, _updateFunc, _internalUpdatingStopSource.get_token()); if (st == std::future_status::ready) {
// auto status = update_ft.wait_for(timeout); return _lastUpdateError;
} else if (st == std::future_status::deferred) {
// if (status == std::future_status::ready) { ft.get();
// *_isDataUpdated = true; _lastUpdateError = MccTelemetryErrorCode::ERROR_DATA_TIMEOUT;
// _lastUpdateError = update_ft.get(); } else {
// } else if (status == std::future_status::deferred) { // std::async was invoked in this thread, get _lastUpdateError = MccTelemetryErrorCode::ERROR_DATA_TIMEOUT;
// result
// _lastUpdateError = update_ft.get();
// if (!_lastUpdateError) {
// *_isDataUpdated = true;
// }
// } else { // timeout
// stop_source.request_stop();
// _lastUpdateError = MccTelemetryErrorCode::ERROR_DATA_TIMEOUT;
// }
_lastUpdateError = _updateFunc(_internalUpdatingStopSource.get_token());
*_isDataUpdated = true;
} }
// unblock waiting threads even in the case of timeout! return _lastUpdateError;
_updateCondVar->notify_all();
// *_isDataUpdated = false;
// trigger updating
_dataUpdatingRequested->test_and_set();
_dataUpdatingRequested->notify_one();
// wait for updating start
_dataUpdatingStart->wait(false);
if (_timeoutMutex->try_lock_for(timeout)) {
_timeoutMutex->unlock();
} else {
_lastUpdateError = MccTelemetryErrorCode::ERROR_DATA_TIMEOUT;
}
return _lastUpdateError; return _lastUpdateError;
} }
// block the thread and wait for data to be ready (internal synchronization) // block the thread and wait for data to be ready (internal synchronization)
error_t waitForTelemetryData(mcc_telemetry_data_c auto* tdata, traits::mcc_time_duration_c auto const& timeout) template <traits::mcc_time_duration_c DT = decltype(MccTelemetry::defaultUpdatingTimeout)>
error_t waitForTelemetryData(mcc_telemetry_data_c auto* tdata,
const DT& timeout = MccTelemetry::defaultUpdatingTimeout)
{ {
if (tdata == nullptr) { if (tdata == nullptr) {
return MccTelemetryErrorCode::ERROR_NULLPTR; return MccTelemetryErrorCode::ERROR_NULLPTR;
} }
std::unique_lock ulock(*_updateMutex);
auto res = _updateCondVar->wait_for(ulock, timeout, [this]() -> bool { return *_isDataUpdated; }); updateTelemetryData(timeout);
if (!res) {
return MccTelemetryErrorCode::ERROR_DATA_TIMEOUT;
}
// std::lock_guard thread_lock{*_updateMutex};
if (!_lastUpdateError) { if (!_lastUpdateError) {
std::lock_guard thread_lock{*_updateMutex};
mcc_copy_telemetry_data(_data, tdata); mcc_copy_telemetry_data(_data, tdata);
} }
return _lastUpdateError; return _lastUpdateError;
} }
@@ -643,6 +922,10 @@ public:
std::lock_guard lock{*_updateMutex}; std::lock_guard lock{*_updateMutex};
mcc_copy_celestial_point(pt, &_data.entered_target);
/*
mcc_copy_celestial_point(pt, &_userTarget); mcc_copy_celestial_point(pt, &_userTarget);
@@ -669,6 +952,8 @@ public:
return MccTelemetryErrorCode::ERROR_UNSUPPORTED_COORD_PAIR; return MccTelemetryErrorCode::ERROR_UNSUPPORTED_COORD_PAIR;
} }
*/
return _updateTargetFunc({}); return _updateTargetFunc({});
// return _setTargetFunc(pt); // return _setTargetFunc(pt);
@@ -742,13 +1027,40 @@ protected:
std::unique_ptr<std::condition_variable> _updateCondVar; std::unique_ptr<std::condition_variable> _updateCondVar;
std::future<void> _updatingFuture{}; std::future<void> _updatingFuture{};
std::unique_ptr<std::atomic_bool> _dataUpdatingRequested{new std::atomic_bool{false}}; std::unique_ptr<std::atomic_flag> _dataUpdatingRequested{new std::atomic_flag{}};
std::unique_ptr<std::atomic_flag> _dataUpdatingStart{new std::atomic_flag{}};
std::unique_ptr<std::timed_mutex> _timeoutMutex{new std::timed_mutex()}; std::unique_ptr<std::timed_mutex> _timeoutMutex{new std::timed_mutex()};
error_t _lastUpdateError{MccTelemetryErrorCode::ERROR_OK}; error_t _lastUpdateError{MccTelemetryErrorCode::ERROR_OK};
void updateLoop(std::stop_token stoken)
{
bool stop_flag = stoken.stop_requested();
// controls->logTrace(std::format("stop_requested() = {}", stop_flag));
while (!stoken.stop_requested()) {
// while (!stop_flag) {
_dataUpdatingRequested->wait(false);
// stop_flag = stoken.stop_requested();
// if (!stop_flag) {
if (!stoken.stop_requested()) {
*_internalUpdating = true;
std::lock_guard lock{*_timeoutMutex};
_dataUpdatingStart->test_and_set();
_dataUpdatingStart->notify_all();
_lastUpdateError = _updateFunc(stoken);
_dataUpdatingStart->clear();
_dataUpdatingRequested->clear();
}
}
}
}; };
static_assert(mcc_telemetry_c<MccTelemetry>, ""); static_assert(mcc_telemetry_c<MccTelemetry>, "");
} // namespace mcc } // namespace mcc

View File

@@ -6,6 +6,8 @@
/* SIMPLE Tracking MODEL IMPLEMENTATION */ /* SIMPLE Tracking MODEL IMPLEMENTATION */
#include <fstream>
#include "mcc_defaults.h" #include "mcc_defaults.h"
#include "mcc_moving_model_common.h" #include "mcc_moving_model_common.h"
@@ -22,6 +24,7 @@ enum class MccSimpleTrackingModelErrorCode : int {
ERROR_DIST_TELEMETRY, ERROR_DIST_TELEMETRY,
ERROR_PZONE_CONTAINER_COMP, ERROR_PZONE_CONTAINER_COMP,
ERROR_NEAR_PZONE, ERROR_NEAR_PZONE,
ERROR_IN_PZONE,
ERROR_ALREADY_TRACK, ERROR_ALREADY_TRACK,
ERROR_ALREADY_STOPPED, ERROR_ALREADY_STOPPED,
ERROR_STOPPED ERROR_STOPPED
@@ -78,6 +81,8 @@ struct MccSimpleTrackingModelCategory : public std::error_category {
return "pzone container computation error"; return "pzone container computation error";
case MccSimpleTrackingModelErrorCode::ERROR_NEAR_PZONE: case MccSimpleTrackingModelErrorCode::ERROR_NEAR_PZONE:
return "near prohibited zone"; return "near prohibited zone";
case MccSimpleTrackingModelErrorCode::ERROR_IN_PZONE:
return "in prohibited zone";
case MccSimpleTrackingModelErrorCode::ERROR_ALREADY_TRACK: case MccSimpleTrackingModelErrorCode::ERROR_ALREADY_TRACK:
return "already tracking"; return "already tracking";
case MccSimpleTrackingModelErrorCode::ERROR_ALREADY_STOPPED: case MccSimpleTrackingModelErrorCode::ERROR_ALREADY_STOPPED:
@@ -110,35 +115,75 @@ public:
typedef MccSimpleMovingModelParams tracking_params_t; typedef MccSimpleMovingModelParams tracking_params_t;
template <mcc_all_controls_c CONTROLS_T> template <mcc_all_controls_c CONTROLS_T, mcc_logger_c LoggerT = MccNullLogger>
MccSimpleTrackingModel(CONTROLS_T* controls) MccSimpleTrackingModel(CONTROLS_T* controls, LoggerT logger)
: _stopTracking(new std::atomic_bool()), _currentParamsMutex(new std::mutex()) : _stopTracking(new std::atomic_bool()),
_currentParamsMutex(new std::mutex()),
_lastError(MccSimpleTrackingModelErrorCode::ERROR_OK)
{ {
std::ostringstream os;
os << std::this_thread::get_id();
logger.logDebug(std::format("Create MccSimpleTrackingModel class instance (thread: {})", os.str()));
*_stopTracking = true; *_stopTracking = true;
_trackingFunc = [controls, this]() -> error_t { _trackingFunc = [logger = std::move(logger), controls, this]() mutable -> error_t {
typename CONTROLS_T::hardware_state_t hw_state; typename CONTROLS_T::hardware_state_t hw_state;
MccTelemetryData tdata; MccTelemetryData tdata;
MccEqtHrzCoords intsc_coords; MccEqtHrzCoords intsc_coords;
MccCelestialPoint target_in_future_pt; MccCelestialPoint target_in_future_pt;
bool store_path = false;
std::ofstream fst;
using path_tp_t = std::chrono::duration<double>; // seconds represented as double
if constexpr (mccIsEquatorialMount(CONTROLS_T::mountType)) { if constexpr (mccIsEquatorialMount(CONTROLS_T::mountType)) {
target_in_future_pt.pair_kind = MccCoordPairKind::COORDS_KIND_HADEC_APP; target_in_future_pt.pair_kind = MccCoordPairKind::COORDS_KIND_HADEC_APP;
} else if constexpr (mccIsAltAzMount(CONTROLS_T::mountType)) { } else if constexpr (mccIsAltAzMount(CONTROLS_T::mountType)) {
target_in_future_pt.pair_kind = MccCoordPairKind::COORDS_KIND_AZZD; target_in_future_pt.pair_kind = MccCoordPairKind::COORDS_KIND_AZZD;
} else { } else {
static_assert(false, "UNKNOW MOUNT TYPE!"); static_assert(false, "UNKNOWN MOUNT TYPE!");
} }
// double dist, dx, dy; // double dist, dx, dy;
logger.logInfo("Start tracking:");
logger.logInfo(" timesift: {} millisecs", _currentParams.timeShiftToTargetPoint.count());
logger.logInfo(" min time to pzone: {} secs", _currentParams.minTimeToPZone.count());
auto t_err = controls->telemetryData(&tdata); auto t_err = controls->telemetryData(&tdata);
if (t_err) { if (t_err) {
*_stopTracking = true; *_stopTracking = true;
return mcc_deduce_error_code(t_err, MccSimpleTrackingModelErrorCode::ERROR_GET_TELEMETRY); return mcc_deduce_error_code(t_err, MccSimpleTrackingModelErrorCode::ERROR_GET_TELEMETRY);
} }
auto start_point = tdata.time_point; // needed for trajectory file
bool in_zone;
auto pz_err = controls->inPZone(tdata, &in_zone);
if (pz_err) {
return mcc_deduce_error_code(pz_err, MccSimpleTrackingModelErrorCode::ERROR_PZONE_CONTAINER_COMP);
}
if (in_zone) {
logger.logError("mount current coordinates are in prohibited zone:");
logger.logError(std::format(
" RA-APP, DEC-APP, HA, LST: {}, {}, {}, {}", mcc::MccAngle{tdata.RA_APP}.sexagesimal(true),
mcc::MccAngle{tdata.DEC_APP}.sexagesimal(), mcc::MccAngle{tdata.HA}.sexagesimal(true),
mcc::MccAngle{tdata.LST}.sexagesimal(true)));
logger.logError(std::format(" AZ, ZD, ALT: {}, {}, {}", mcc::MccAngle{tdata.target.AZ}.sexagesimal(),
mcc::MccAngle{tdata.ZD}.sexagesimal(),
mcc::MccAngle{tdata.ALT}.sexagesimal()));
logger.logError(std::format(" hardware X, Y: {}, {}", mcc::MccAngle{tdata.X}.sexagesimal(),
mcc::MccAngle{tdata.Y}.sexagesimal()));
return MccSimpleTrackingModelErrorCode::ERROR_IN_PZONE;
}
bool no_intersects = false; bool no_intersects = false;
@@ -168,7 +213,7 @@ public:
no_intersects = true; no_intersects = true;
} }
} else { } else {
static_assert(false, "UNKNOW MOUNT TYPE!"); static_assert(false, "UNKNOWN MOUNT TYPE!");
} }
return MccSimpleTrackingModelErrorCode::ERROR_OK; return MccSimpleTrackingModelErrorCode::ERROR_OK;
@@ -176,17 +221,23 @@ public:
auto target_point = [&, this](MccCelestialPoint* point) -> std::error_code { auto target_point = [&, this](MccCelestialPoint* point) -> std::error_code {
auto dt = std::chrono::duration<double>{tdata.HA} + double dha =
_currentParams.timeShiftToTargetPoint * mcc_sideral_to_UT1_ratio; // hour seconds std::chrono::duration<double>(_currentParams.timeShiftToTargetPoint * mcc_sideral_to_UT1_ratio)
.count(); // sideral hour seconds
dha *= std::numbers::pi / 43200.0; // radians
auto target_ha = tdata.target.HA + dha;
// auto dt = std::chrono::duration<double>{tdata.HA} +
// _currentParams.timeShiftToTargetPoint * mcc_sideral_to_UT1_ratio; // hour seconds
auto tp_dt = std::chrono::duration_cast<typename decltype(tdata.time_point)::duration>( auto tp_dt = std::chrono::duration_cast<typename decltype(tdata.time_point)::duration>(
_currentParams.timeShiftToTargetPoint); _currentParams.timeShiftToTargetPoint);
// point in +time_dist future // point in +time_dist future
MccCelestialPoint pt{ MccCelestialPoint pt{.pair_kind = MccCoordPairKind::COORDS_KIND_HADEC_APP,
.pair_kind = MccCoordPairKind::COORDS_KIND_HADEC_APP, .X = MccAngle(target_ha).normalize<MccAngle::NORM_KIND_180_180>(),
.X = MccAngle(dt.count() * std::numbers::pi / 3600.0 / 15.0).normalize<MccAngle::NORM_KIND_0_360>(), .Y = tdata.target.DEC_APP};
.Y = tdata.DEC_APP};
mcc_tp2tp(tdata.time_point + tp_dt, pt.time_point); mcc_tp2tp(tdata.time_point + tp_dt, pt.time_point);
point->time_point = pt.time_point; point->time_point = pt.time_point;
@@ -215,14 +266,14 @@ public:
return mcc_deduce_error_code(pcm_err, MccSimpleTrackingModelErrorCode::ERROR_PCM_COMP); return mcc_deduce_error_code(pcm_err, MccSimpleTrackingModelErrorCode::ERROR_PCM_COMP);
} }
mcc_tp2tp(tdata.time_point, hw_state.time_point); mcc_tp2tp(tdata.time_point + tp_dt, hw_state.time_point);
} }
return MccSimpleTrackingModelErrorCode::ERROR_OK; return MccSimpleTrackingModelErrorCode::ERROR_OK;
}; };
auto pz_err = update_pzones_ipoint(); pz_err = update_pzones_ipoint();
if (pz_err) { if (pz_err) {
*_stopTracking = true; *_stopTracking = true;
return mcc_deduce_error_code(pz_err, MccSimpleTrackingModelErrorCode::ERROR_PZONE_CONTAINER_COMP); return mcc_deduce_error_code(pz_err, MccSimpleTrackingModelErrorCode::ERROR_PZONE_CONTAINER_COMP);
@@ -233,6 +284,8 @@ public:
{ {
std::lock_guard lock{*_currentParamsMutex}; std::lock_guard lock{*_currentParamsMutex};
mcc_copy_eqt_hrz_coord(tdata, &tdata.target);
auto ccte_err = target_point(&target_in_future_pt); auto ccte_err = target_point(&target_in_future_pt);
if (ccte_err) { if (ccte_err) {
*_stopTracking = true; *_stopTracking = true;
@@ -243,15 +296,54 @@ public:
hw_state.speedX = _currentParams.trackSpeedX; hw_state.speedX = _currentParams.trackSpeedX;
hw_state.speedY = _currentParams.trackSpeedY; hw_state.speedY = _currentParams.trackSpeedY;
} }
logger.logTrace("The updated target point:");
if constexpr (mccIsEquatorialMount(CONTROLS_T::mountType)) {
logger.logTrace(" HA, DEC: {} {}", MccAngle(target_in_future_pt.X).sexagesimal(true),
MccAngle(target_in_future_pt.Y).sexagesimal());
} else if constexpr (mccIsAltAzMount(CONTROLS_T::mountType)) {
logger.logTrace(" AZ, ZD: {} {}", MccAngle(target_in_future_pt.X).sexagesimal(),
MccAngle(target_in_future_pt.Y).sexagesimal());
} else {
static_assert(false, "UNKNOWN MOUNT TYPE!");
}
if (!_currentParams.trackingPathFilename.empty()) { // open tracking trajectory file
fst.open(_currentParams.trackingPathFilename);
if (fst.is_open()) {
store_path = true;
logger.logInfo(" timesift: {} millisecs", _currentParams.timeShiftToTargetPoint.count());
logger.logInfo(" min time to pzone: {} secs", _currentParams.minTimeToPZone.count());
fst << "# \n";
fst << "# Tracking trajectory, " << std::chrono::system_clock::now() << "\n";
fst << "# Config:\n";
fst << "# timeshift: " << _currentParams.timeShiftToTargetPoint.count() << " millisecs\n";
fst << "# min time to pzone: " << _currentParams.minTimeToPZone.count() << " secs\n";
fst << "# \n";
fst << "# Format (time is in seconds, coordinates are in radians): \n";
fst << "# <time-since-start> <target X> <target Y> <mount X> <mount Y> <dX_{target-mount}> "
"<dY_{target-mount}> <moving state>\n";
} else {
logger.logError(std::format("Cannot open tracking path file: {}! Do not save it!",
_currentParams.trackingPathFilename));
}
}
} }
// move mount // move mount
logger.logDebug(std::format("Send to hardware: X = {} degs, Y = {} degs",
mcc::MccAngle{hw_state.X}.degrees(), mcc::MccAngle{hw_state.Y}.degrees()));
auto hw_err = controls->hardwareSetState(hw_state); auto hw_err = controls->hardwareSetState(hw_state);
if (hw_err) { if (hw_err) {
*_stopTracking = true; *_stopTracking = true;
return mcc_deduce_error_code(hw_err, MccSimpleTrackingModelErrorCode::ERROR_HW_SETSTATE); return mcc_deduce_error_code(hw_err, MccSimpleTrackingModelErrorCode::ERROR_HW_SETSTATE);
} }
logger.logDebug(" the 'hardwareSetState' method performed successfully!");
std::chrono::steady_clock::time_point last_corr_tp, last_ipzone_update_tp; std::chrono::steady_clock::time_point last_corr_tp, last_ipzone_update_tp;
last_corr_tp = std::chrono::steady_clock::now(); last_corr_tp = std::chrono::steady_clock::now();
@@ -270,57 +362,126 @@ public:
} }
} }
if (store_path) {
fst << std::chrono::duration_cast<path_tp_t>(tdata.time_point - start_point).count() << " "
<< tdata.target.HA << " " << tdata.target.DEC_APP << " " << tdata.HA << " " << tdata.DEC_APP
<< " " << (tdata.target.HA - tdata.HA) << " " << (tdata.target.DEC_APP - tdata.DEC_APP) << " "
<< (int)hw_state.moving_state << "\n";
}
if constexpr (mccIsEquatorialMount(CONTROLS_T::mountType)) {
logger.logTrace(std::format(" current target: HA = {}, DEC = {}",
mcc::MccAngle(tdata.target.HA).sexagesimal(true),
mcc::MccAngle(tdata.target.DEC_APP).sexagesimal()));
logger.logTrace(std::format(" current mount: HA = {}, DEC = {}",
mcc::MccAngle(tdata.HA).sexagesimal(true),
mcc::MccAngle(tdata.DEC_APP).sexagesimal()));
} else if constexpr (mccIsAltAzMount(CONTROLS_T::mountType)) {
logger.logTrace(std::format(" target: AZ = {}, ZD = {}",
mcc::MccAngle(tdata.target.AZ).sexagesimal(),
mcc::MccAngle(tdata.target.ZD).sexagesimal()));
logger.logTrace(std::format(" mount: AZ = {}, ZD = {}", mcc::MccAngle(tdata.AZ).sexagesimal(),
mcc::MccAngle(tdata.ZD).sexagesimal()));
}
logger.logTrace(std::format(" mount: speedX = {}/s, speedY = {}/s",
mcc::MccAngleFancyString(tdata.speedX),
mcc::MccAngleFancyString(tdata.speedY)));
if (*_stopTracking) { if (*_stopTracking) {
break; break;
} }
// control prohibited zones // control prohibited zones
if (mcc_is_near_pzones(controls, tdata, _currentParams.minTimeToPZone, pz_err)) { // if (mcc_is_near_pzones(controls, tdata, _currentParams.minTimeToPZone, pz_err)) {
*_stopTracking = true; // logger.logError("Mount is near zone!");
return MccSimpleTrackingModelErrorCode::ERROR_NEAR_PZONE; // *_stopTracking = true;
} // return MccSimpleTrackingModelErrorCode::ERROR_NEAR_PZONE;
if (pz_err) { // }
*_stopTracking = true; // if (pz_err) {
return mcc_deduce_error_code(pz_err, MccSimpleTrackingModelErrorCode::ERROR_PZONE_CONTAINER_COMP); // *_stopTracking = true;
} // return mcc_deduce_error_code(pz_err,
// MccSimpleTrackingModelErrorCode::ERROR_PZONE_CONTAINER_COMP);
// }
if (*_stopTracking) { if (*_stopTracking) {
break; break;
} }
{ // {
std::lock_guard lock{*_currentParamsMutex}; // std::lock_guard lock{*_currentParamsMutex};
auto now = std::chrono::steady_clock::now(); // auto now = std::chrono::steady_clock::now();
if ((now - last_corr_tp) < _currentParams.trackingCycleInterval) { // if ((now - last_corr_tp) > _currentParams.trackingCycleInterval) {
continue; // // update prohibited zones intersection point
} // if ((now - last_ipzone_update_tp) < _currentParams.updatingPZoneInterval) {
// pz_err = update_pzones_ipoint();
// if (pz_err) {
// *_stopTracking = true;
// return mcc_deduce_error_code(
// pz_err, MccSimpleTrackingModelErrorCode::ERROR_PZONE_CONTAINER_COMP);
// }
// }
// update prohibited zones intersection point // // compute new target-in-future point
if ((now - last_ipzone_update_tp) < _currentParams.updatingPZoneInterval) { // auto ccte_err = target_point(&target_in_future_pt);
pz_err = update_pzones_ipoint(); // if (ccte_err) {
if (pz_err) { // *_stopTracking = true;
*_stopTracking = true; // return mcc_deduce_error_code(ccte_err, MccSimpleTrackingModelErrorCode::ERROR_CCTE);
return mcc_deduce_error_code(pz_err, // }
MccSimpleTrackingModelErrorCode::ERROR_PZONE_CONTAINER_COMP);
}
}
// compute new target-in-future point // logger.logTrace("The updated target point:");
auto ccte_err = target_point(&target_in_future_pt); // if constexpr (mccIsEquatorialMount(CONTROLS_T::mountType)) {
if (ccte_err) { // logger.logTrace(" HA, DEC: {} {}", MccAngle(target_in_future_pt.X).sexagesimal(true),
*_stopTracking = true; // MccAngle(target_in_future_pt.Y).sexagesimal());
return mcc_deduce_error_code(ccte_err, MccSimpleTrackingModelErrorCode::ERROR_CCTE); // } else if constexpr (mccIsAltAzMount(CONTROLS_T::mountType)) {
} // logger.logTrace(" AZ, ZD: {} {}", MccAngle(target_in_future_pt.X).sexagesimal(),
// MccAngle(target_in_future_pt.Y).sexagesimal());
// } else {
// static_assert(false, "UNKNOWN MOUNT TYPE!");
// }
// }
// }
// compute new target-in-future point
auto ccte_err = target_point(&target_in_future_pt);
if (ccte_err) {
*_stopTracking = true;
return mcc_deduce_error_code(ccte_err, MccSimpleTrackingModelErrorCode::ERROR_CCTE);
}
logger.logTrace("The updated target point:");
if constexpr (mccIsEquatorialMount(CONTROLS_T::mountType)) {
logger.logTrace(" HA, DEC: {} {}", MccAngle(target_in_future_pt.X).sexagesimal(true),
MccAngle(target_in_future_pt.Y).sexagesimal());
} else if constexpr (mccIsAltAzMount(CONTROLS_T::mountType)) {
logger.logTrace(" AZ, ZD: {} {}", MccAngle(target_in_future_pt.X).sexagesimal(),
MccAngle(target_in_future_pt.Y).sexagesimal());
} else {
static_assert(false, "UNKNOWN MOUNT TYPE!");
} }
// send corrections // send corrections
logger.logDebug(std::format("Send to hardware: X = {} degs, Y = {} degs",
mcc::MccAngle{hw_state.X}.degrees(), mcc::MccAngle{hw_state.Y}.degrees()));
hw_state.moving_state = CONTROLS_T::hardware_moving_state_t::HW_MOVE_TRACKING; hw_state.moving_state = CONTROLS_T::hardware_moving_state_t::HW_MOVE_TRACKING;
hw_err = controls->hardwareSetState(hw_state); hw_err = controls->hardwareSetState(hw_state);
if (hw_err) { if (hw_err) {
*_stopTracking = true; *_stopTracking = true;
return mcc_deduce_error_code(hw_err, MccSimpleTrackingModelErrorCode::ERROR_HW_SETSTATE); return mcc_deduce_error_code(hw_err, MccSimpleTrackingModelErrorCode::ERROR_HW_SETSTATE);
} }
logger.logDebug(" the 'hardwareSetState' method performed successfully!");
// sleep here
{
std::lock_guard lock{*_currentParamsMutex};
std::this_thread::sleep_for(_currentParams.trackingTelemetryInterval);
}
} }
return MccSimpleTrackingModelErrorCode::ERROR_OK; return MccSimpleTrackingModelErrorCode::ERROR_OK;
@@ -372,6 +533,10 @@ public:
return _currentParams; return _currentParams;
} }
error_t trackinLastError() const
{
return _lastError;
}
protected: protected:
std::function<error_t()> _trackingFunc{}; std::function<error_t()> _trackingFunc{};
@@ -379,6 +544,8 @@ protected:
tracking_params_t _currentParams{}; tracking_params_t _currentParams{};
std::unique_ptr<std::mutex> _currentParamsMutex{}; std::unique_ptr<std::mutex> _currentParamsMutex{};
error_t _lastError;
}; };
} // namespace mcc } // namespace mcc

View File

@@ -244,6 +244,10 @@ static R rad2sxg(double ang, bool hms = false, int prec = 2)
term *= 10.0; term *= 10.0;
} }
// round to given precision of arcseconds/seconds
degs = std::round(degs * 3600.0 * term) / term / 3600.0;
auto d = std::trunc(degs); auto d = std::trunc(degs);
auto s = (degs - d) * 60.0; auto s = (degs - d) * 60.0;
auto m = std::trunc(s); auto m = std::trunc(s);
@@ -261,7 +265,9 @@ static R rad2sxg(double ang, bool hms = false, int prec = 2)
} }
if (ang < 0) { if (ang < 0) {
std::ranges::copy(std::string_view("-"), std::back_inserter(res)); if (!isEqual(d, 0.0) || !isEqual(m, 0.0) || !isEqual(s, 0.0)) {
std::ranges::copy(std::string_view("-"), std::back_inserter(res));
}
} }
std::vformat_to(std::back_inserter(res), std::string_view{fmt.begin(), fmt.end()}, std::make_format_args(d, m, s)); std::vformat_to(std::back_inserter(res), std::string_view{fmt.begin(), fmt.end()}, std::make_format_args(d, m, s));

View File

@@ -176,6 +176,8 @@ int main()
// std::cout << "DEC_ICRS = " << mcc::MccAngle(cp.Y).sexagesimal() << "\n\n"; // std::cout << "DEC_ICRS = " << mcc::MccAngle(cp.Y).sexagesimal() << "\n\n";
std::cout << "time point = " << eqhrz.time_point << "\n"; std::cout << "time point = " << eqhrz.time_point << "\n";
std::cout << "RA_ICRS = " << mcc::MccAngle(eqhrz.RA_ICRS).sexagesimal(true) << "\n";
std::cout << "DEC_ICRS = " << mcc::MccAngle(eqhrz.DEC_ICRS).sexagesimal() << "\n";
std::cout << "RA_APP = " << mcc::MccAngle(eqhrz.RA_APP).sexagesimal(true) << "\n"; std::cout << "RA_APP = " << mcc::MccAngle(eqhrz.RA_APP).sexagesimal(true) << "\n";
std::cout << "DEC_APP = " << mcc::MccAngle(eqhrz.DEC_APP).sexagesimal() << "\n"; std::cout << "DEC_APP = " << mcc::MccAngle(eqhrz.DEC_APP).sexagesimal() << "\n";
std::cout << "HA = " << mcc::MccAngle(eqhrz.HA).sexagesimal(true) << "\n"; std::cout << "HA = " << mcc::MccAngle(eqhrz.HA).sexagesimal(true) << "\n";

View File

@@ -0,0 +1,85 @@
#include <iostream>
#include "../mcc_ccte_erfa_new.h"
#include "../mcc_coord.h"
using namespace mcc;
typedef MccGenericSkyPoint<mcc::ccte::erfa::MccCCTE_ERFA> skypt_t;
static skypt_t::ccte_t::engine_state_t saoras{.meteo{.temperature = 0.0, .humidity = 0.5, .pressure = 1010.0},
.wavelength = 0.5,
.lat = 43.646711_degs,
.lon = 41.440732_degs,
.elev = 2100.0};
// skypt_t::cctEngine.setStateERFA(saoras);
static_assert(mcc_angle_c<double>, "!!!!!!!!!!!!");
int main()
{
skypt_t::cctEngine.setStateERFA(saoras);
skypt_t pt;
MccSkyRADEC_ICRS icrs(0.0, 70.0_degs);
pt = icrs;
MccSkyRADEC_OBS radec_obs{0.0, 0.0};
MccSkyRADEC_APP radec_app;
MccSkyAZALT azalt{0, 0};
MccSkyAZZD azzd{0, 0};
MccSkyHADEC_OBS hadec_obs;
MccAngle eo, lst;
skypt_t::cctEngine.equationOrigins(radec_obs.epoch(), &eo);
skypt_t::cctEngine.apparentSideralTime(radec_obs.epoch(), &lst, true);
std::cout << "EO = " << eo.sexagesimal(true) << "\n";
std::cout << "LST = " << lst.sexagesimal(true) << "\n\n";
pt.to(radec_obs, azalt, azzd, radec_app);
std::cout << "FROM ICRS TO OBSERVED:\n";
std::cout << "RA_ICRS = " << icrs.x().sexagesimal(true) << "\n";
std::cout << "DEC_ICRS = " << icrs.y().sexagesimal() << "\n";
std::cout << "OBS COORD EPOCH: " << radec_obs.epoch().UTC() << "\n";
std::cout << "RA_OBS = " << radec_obs.x().sexagesimal(true) << "\n";
std::cout << "DEC_OBS = " << radec_obs.y().sexagesimal() << "\n";
std::cout << "AZ = " << azalt.x().sexagesimal() << "\n";
std::cout << "ALT = " << azalt.y().sexagesimal() << "\n";
std::cout << "ZD = " << azzd.y().sexagesimal() << "\n";
std::cout << "RA_APP = " << radec_app.x().sexagesimal(true) << "\n";
std::cout << "DEC_APP = " << radec_app.y().sexagesimal() << "\n";
// radec_obs = {10.2387983_degs, "43:21:34.5465"_dms};
icrs.setX(111.0_degs), icrs.setY(111.0_degs);
azzd.setX(111.0_degs), azzd.setY(111.0_degs);
azalt.setX(111.0_degs), azalt.setY(111.0_degs);
hadec_obs.setX(111.0_degs), hadec_obs.setY(111.0_degs);
pt = radec_obs;
pt.to(icrs, azzd, hadec_obs);
std::cout << "\n\nFROM OBSERVED TO ICRS:\n";
std::cout << "OBS COORD EPOCH: " << radec_obs.epoch().UTC() << "\n";
std::cout << "RA_OBS = " << radec_obs.x().sexagesimal(true) << "\n";
std::cout << "DEC_OBS = " << radec_obs.y().sexagesimal() << "\n";
std::cout << "RA_ICRS = " << icrs.x().sexagesimal(true) << "\n";
std::cout << "DEC_ICRS = " << icrs.y().sexagesimal() << "\n";
std::cout << "\n\nFROM OBSERVED TO OBSERVED:\n";
std::cout << "OBS COORD EPOCH: " << radec_obs.epoch().UTC() << "\n";
std::cout << "RA_OBS = " << radec_obs.x().sexagesimal(true) << "\n";
std::cout << "DEC_OBS = " << radec_obs.y().sexagesimal() << "\n";
std::cout << "HA_OBS = " << hadec_obs.x().sexagesimal(true) << "\n";
std::cout << "AZ = " << azzd.x().sexagesimal() << "\n";
std::cout << "ZD = " << azzd.y().sexagesimal() << "\n";
azalt = pt;
std::cout << "ALT = " << azalt.y().sexagesimal() << "\n";
return 0;
}