Instrument Neutral Distributed Interface INDI 2.2.5
device_manager.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2025 Jasem Mutlaq. All rights reserved.
3
4 INDI Alpaca Device Manager
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the Free
8 Software Foundation; either version 2 of the License, or (at your option)
9 any later version.
10
11 This program is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 more details.
15
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
20
21 The full GNU General Public License is included in this distribution in the
22 file called LICENSE.
23*******************************************************************************/
24
25#include "device_manager.h"
29#include "alpaca_client.h"
30#include "indilogger.h"
31
32#include <cstring>
33#include <sstream>
34
35#ifdef _USE_SYSTEM_JSONLIB
36#include <nlohmann/json.hpp>
37#else
38#include <indijson.hpp>
39#endif
40
42
43// Static instance for singleton
45{
46 static DeviceManager instance;
47 return &instance;
48}
49
50DeviceManager::DeviceManager()
51{
52 DEBUGDEVICE("INDI Alpaca Server", INDI::Logger::DBG_SESSION, "Device manager initialized");
53}
54
56{
57 DEBUGDEVICE("INDI Alpaca Server", INDI::Logger::DBG_SESSION, "Device manager destroyed");
58}
59
60void DeviceManager::setAlpacaClient(const std::shared_ptr<AlpacaClient> &client)
61{
62 m_Client = client;
63 DEBUGDEVICE("INDI Alpaca Server", INDI::Logger::DBG_SESSION, "AlpacaClient set");
64}
65
67{
68 if (m_Client)
69 {
70 DEBUGFDEVICE("INDI Alpaca Server", INDI::Logger::DBG_DEBUG, "Sending new number property: %s", numberProperty.getName());
71 m_Client->sendNewNumber(numberProperty);
72 }
73 else
74 {
75 DEBUGDEVICE("INDI Alpaca Server", INDI::Logger::DBG_ERROR, "Cannot send new number property: AlpacaClient not set");
76 }
77}
78
80{
81 if (m_Client)
82 {
83 DEBUGFDEVICE("INDI Alpaca Server", INDI::Logger::DBG_DEBUG, "Sending new switch property: %s", switchProperty.getName());
84 m_Client->sendNewSwitch(switchProperty);
85 }
86 else
87 {
88 DEBUGDEVICE("INDI Alpaca Server", INDI::Logger::DBG_ERROR, "Cannot send new switch property: AlpacaClient not set");
89 }
90}
91
93{
94 std::lock_guard<std::mutex> lock(m_Mutex);
95
96 const char *deviceName = device.getDeviceName();
97 DEBUGFDEVICE("INDI Alpaca Server", INDI::Logger::DBG_SESSION, "Adding device: %s", deviceName);
98
99 // Check if device already exists
100 if (m_Devices.find(deviceName) != m_Devices.end())
101 DEBUGFDEVICE("INDI Alpaca Server", INDI::Logger::DBG_SESSION, "Device %s already exists, updating", deviceName);
102
103 m_Devices[deviceName] = device;
104
105 createBridgeIfReady(device);
106}
107
108void DeviceManager::createBridgeIfReady(INDI::BaseDevice device)
109{
110 const char *deviceName = device.getDeviceName();
111
112 // Check if we already have a bridge for this device
113 if (m_DeviceNumberMap.find(deviceName) != m_DeviceNumberMap.end())
114 return;
115
116 // Check if device is ready (has non-zero interface)
117 uint32_t interface = device.getDriverInterface();
118 if (interface == 0)
119 {
120 DEBUGFDEVICE("INDI Alpaca Server", INDI::Logger::DBG_DEBUG,
121 "Device %s not ready yet (interface = 0), waiting for driver info", deviceName);
122 return;
123 }
124
125 std::string deviceType = getAlpacaDeviceType(interface);
126 if (deviceType.empty())
127 {
128 DEBUGFDEVICE("INDI Alpaca Server", INDI::Logger::DBG_WARNING, "Unsupported device interface: %u for device %s", interface,
129 deviceName);
130 return;
131 }
132
133 // Alpaca numbers devices from 0 within each device type, use the lowest free number
134 int deviceNumber = 0;
135 while (m_Bridges.find({deviceType, deviceNumber}) != m_Bridges.end())
136 deviceNumber++;
137
138 auto bridge = createBridge(device, deviceNumber);
139 if (!bridge)
140 {
141 DEBUGFDEVICE("INDI Alpaca Server", INDI::Logger::DBG_ERROR, "Failed to create bridge for device %s", deviceName);
142 return;
143 }
144
145 m_Bridges[{deviceType, deviceNumber}] = std::move(bridge);
146 m_DeviceNumberMap[deviceName] = {deviceType, deviceNumber};
147 DEBUGFDEVICE("INDI Alpaca Server", INDI::Logger::DBG_SESSION, "Created %s bridge for device %s with number %d",
148 deviceType.c_str(), deviceName, deviceNumber);
149}
150
152{
153 std::lock_guard<std::mutex> lock(m_Mutex);
154
155 const char *deviceName = device.getDeviceName();
156 DEBUGFDEVICE("INDI Alpaca Server", INDI::Logger::DBG_SESSION, "Removing device: %s", deviceName);
157
158 // Check if device exists
159 auto it = m_DeviceNumberMap.find(deviceName);
160 if (it == m_DeviceNumberMap.end())
161 {
162 DEBUGFDEVICE("INDI Alpaca Server", INDI::Logger::DBG_SESSION, "Device %s not found", deviceName);
163 return;
164 }
165
166 // Remove bridge
167 BridgeKey key = it->second;
168 m_Bridges.erase(key);
169 m_DeviceNumberMap.erase(it);
170 m_Devices.erase(deviceName);
171
172 DEBUGFDEVICE("INDI Alpaca Server", INDI::Logger::DBG_SESSION, "Removed %s device %s with number %d", key.first.c_str(),
173 deviceName, key.second);
174}
175
177{
178 std::lock_guard<std::mutex> lock(m_Mutex);
179
180 const char *deviceName = property.getDeviceName();
181 DEBUGFDEVICE("INDI Alpaca Server", INDI::Logger::DBG_DEBUG, "Updating property for device %s: %s", deviceName,
182 property.getName());
183
184 // Check if this is a DRIVER_INFO property update
185 if (strcmp(property.getName(), "DRIVER_INFO") == 0)
186 {
187 // The driver interface may only be known now
188 auto deviceIt = m_Devices.find(deviceName);
189 if (deviceIt != m_Devices.end())
190 createBridgeIfReady(deviceIt->second);
191 }
192
193 // Find device bridge
194 auto it = m_DeviceNumberMap.find(deviceName);
195 if (it == m_DeviceNumberMap.end())
196 {
197 DEBUGFDEVICE("INDI Alpaca Server", INDI::Logger::DBG_DEBUG, "Device %s not found for property update", deviceName);
198 return;
199 }
200
201 // Update bridge
202 auto bridgeIt = m_Bridges.find(it->second);
203 if (bridgeIt != m_Bridges.end())
204 {
205 bridgeIt->second->updateProperty(property);
206 }
207}
208
209std::string DeviceManager::getAlpacaDeviceType(uint32_t interface)
210{
212 return "telescope";
213 if (interface & INDI::BaseDevice::CCD_INTERFACE)
214 return "camera";
215 // Add more device types here as they are implemented
216 // if (interface & INDI::BaseDevice::DOME_INTERFACE)
217 // return "dome";
218
219 return "";
220}
221
222std::unique_ptr<IDeviceBridge> DeviceManager::createBridge(INDI::BaseDevice device, int deviceNumber)
223{
224 // Check device interface to determine type
225 std::string deviceType = getAlpacaDeviceType(device.getDriverInterface());
226
227 // Create appropriate bridge based on interface
228 if (deviceType == "telescope")
229 {
230 DEBUGFDEVICE("INDI Alpaca Server", INDI::Logger::DBG_SESSION, "Creating telescope bridge for device %s",
231 device.getDeviceName());
232 return std::make_unique<TelescopeBridge>(device, deviceNumber);
233 }
234 else if (deviceType == "camera")
235 {
236 DEBUGFDEVICE("INDI Alpaca Server", INDI::Logger::DBG_SESSION, "Creating camera bridge for device %s",
237 device.getDeviceName());
238 return std::make_unique<CameraBridge>(device, deviceNumber);
239 }
240 // Add more device types here as they are implemented
241 // else if (deviceType == "dome")
242 // {
243 // DEBUGFDEVICE("INDI Alpaca Server", INDI::Logger::DBG_SESSION, "Creating dome bridge for device %s", device.getDeviceName());
244 // return std::make_unique<DomeBridge>(device, deviceNumber);
245 // }
246
247 return nullptr;
248}
249
251{
252 DEBUGFDEVICE("INDI Alpaca Server", INDI::Logger::DBG_DEBUG, "Handling Alpaca request: %s", req.path.c_str());
253
254 // Extract transaction IDs from request - do this only once per request
255 int clientTransactionID, serverTransactionID;
256 if (!extractTransactionIDs(req, res, clientTransactionID, serverTransactionID))
257 {
258 // If extraction failed, response has already been set
259 return;
260 }
261
262 // Parse path to determine if it's a management or device API request
263 std::string path = req.path;
264
265 // Management API
266 if (path.find("/management/") == 0)
267 {
268 handleManagementRequest(path.substr(12), req, res, clientTransactionID, serverTransactionID);
269 return;
270 }
271
272 // Device API
273 if (path.find("/api/v1/") == 0)
274 {
275 std::string apiPath = path.substr(8); // Remove "/api/v1/"
276
277 // Parse device type, number, and method
278 std::istringstream ss(apiPath);
279 std::string deviceType, deviceNumberStr, method;
280
281 std::getline(ss, deviceType, '/');
282 std::getline(ss, deviceNumberStr, '/');
283 std::getline(ss, method, '/');
284
285 // Validate
286 if (deviceType.empty() || deviceNumberStr.empty() || method.empty())
287 {
288 json response =
289 {
290 {"ClientTransactionID", clientTransactionID},
291 {"ServerTransactionID", serverTransactionID},
292 {"ErrorNumber", 1001},
293 {"ErrorMessage", "Invalid API request format"}
294 };
295 res.set_content(response.dump(), "application/json");
296 res.status = 400; // Bad Request
297 return;
298 }
299
300 // Check if device type is lowercase
301 std::string deviceTypeLower = deviceType;
302 std::transform(deviceTypeLower.begin(), deviceTypeLower.end(), deviceTypeLower.begin(), ::tolower);
303 if (deviceType != deviceTypeLower)
304 {
305 json response =
306 {
307 {"ClientTransactionID", clientTransactionID},
308 {"ServerTransactionID", serverTransactionID},
309 {"ErrorNumber", 1007},
310 {"ErrorMessage", "Device type must be lowercase"}
311 };
312 res.set_content(response.dump(), "application/json");
313 res.status = 400; // Bad Request
314 return;
315 }
316
317 // Convert device number
318 int deviceNumber;
319 try
320 {
321 deviceNumber = std::stoi(deviceNumberStr);
322 }
323 catch (const std::exception &e)
324 {
325 json response =
326 {
327 {"ClientTransactionID", clientTransactionID},
328 {"ServerTransactionID", serverTransactionID},
329 {"ErrorNumber", 1002},
330 {"ErrorMessage", "Invalid device number"}
331 };
332 res.set_content(response.dump(), "application/json");
333 res.status = 400; // Bad Request
334 return;
335 }
336
337 // Route to appropriate device - pass the transaction IDs
338 routeRequest(deviceNumber, deviceType, method, req, res, clientTransactionID, serverTransactionID);
339 return;
340 }
341
342 // Unknown API
343 json response =
344 {
345 {"ClientTransactionID", clientTransactionID},
346 {"ServerTransactionID", serverTransactionID},
347 {"ErrorNumber", 1000},
348 {"ErrorMessage", "Unknown API endpoint"}
349 };
350 res.set_content(response.dump(), "application/json");
351 res.status = 404; // Not Found
352}
353
354void DeviceManager::routeRequest(int deviceNumber, const std::string &deviceType,
355 const std::string &method,
356 const httplib::Request &req, httplib::Response &res,
357 int clientTransactionID, int serverTransactionID)
358{
359 std::lock_guard<std::mutex> lock(m_Mutex);
360
361 // Find bridge for device type and number
362 auto it = m_Bridges.find({deviceType, deviceNumber});
363 if (it == m_Bridges.end())
364 {
365 json response =
366 {
367 {"ClientTransactionID", clientTransactionID},
368 {"ServerTransactionID", serverTransactionID},
369 {"ErrorNumber", 1003},
370 {"ErrorMessage", "Device not found"}
371 };
372 res.set_content(response.dump(), "application/json");
373 res.status = 404; // Not Found
374 return;
375 }
376
377 // Forward request to bridge
378 it->second->handleRequest(method, req, res);
379
380 // Add transaction IDs to the response
381 try
382 {
383 auto responseJson = json::parse(res.body);
384 responseJson["ClientTransactionID"] = clientTransactionID;
385 responseJson["ServerTransactionID"] = serverTransactionID;
386 res.set_content(responseJson.dump(), "application/json");
387 }
388 catch (const std::exception &e)
389 {
390 // If parsing fails, create a new response with transaction IDs
391 DEBUGDEVICE("INDI Alpaca Server", INDI::Logger::DBG_ERROR,
392 "Failed to parse bridge response JSON, creating new response with transaction IDs");
393
394 json response =
395 {
396 {"ClientTransactionID", clientTransactionID},
397 {"ServerTransactionID", serverTransactionID},
398 {"ErrorNumber", 1006},
399 {"ErrorMessage", "Internal server error: Invalid response format"}
400 };
401 res.set_content(response.dump(), "application/json");
402 }
403}
404
405// Helper method to extract transaction IDs from request
406bool DeviceManager::extractTransactionIDs(const httplib::Request &req, httplib::Response &res, int &clientTransactionID,
407 int &serverTransactionID)
408{
409 // Default values
410 clientTransactionID = 0;
411 serverTransactionID = 0;
412
413 // Extract client ID and client transaction ID from request parameters
414 int clientID = 0;
415
416 // Case-insensitive parameter lookup helper
417 auto getParamValueCaseInsensitive = [&req](const std::string & paramName) -> std::string
418 {
419 // Try exact match first for efficiency
420 if (req.has_param(paramName))
421 return req.get_param_value(paramName);
422
423 // Try case-insensitive match
424 std::string lowerParamName = paramName;
425 std::transform(lowerParamName.begin(), lowerParamName.end(), lowerParamName.begin(), ::tolower);
426
427 for (const auto &param : req.params)
428 {
429 std::string lowerKey = param.first;
430 std::transform(lowerKey.begin(), lowerKey.end(), lowerKey.begin(), ::tolower);
431 if (lowerKey == lowerParamName)
432 return param.second;
433 }
434
435 return "";
436 };
437
438 // Extract ClientID - identifies the client application
439 std::string clientIdStr = getParamValueCaseInsensitive("clientid");
440 if (!clientIdStr.empty())
441 {
442 try
443 {
444 clientID = std::stoi(clientIdStr);
445 }
446 catch (const std::exception &e)
447 {
448 DEBUGDEVICE("INDI Alpaca Server", INDI::Logger::DBG_DEBUG, "Invalid clientid format");
449 }
450 }
451
452 // Extract ClientTransactionID - identifies a specific transaction
453 std::string clientTransactionIdStr = getParamValueCaseInsensitive("clienttransactionid");
454 if (!clientTransactionIdStr.empty())
455 {
456 try
457 {
458 // Check if the string is a valid uint32
459 unsigned long value = std::stoul(clientTransactionIdStr);
460 if (value > UINT32_MAX)
461 {
462 // Invalid range for uint32
463 DEBUGDEVICE("INDI Alpaca Server", INDI::Logger::DBG_DEBUG, "clienttransactionid out of range for uint32");
464
465 // Set response to 400 Bad Request
466 res.status = 400;
467 json response =
468 {
469 {"ClientTransactionID", 0},
470 {"ServerTransactionID", 0},
471 {"ErrorNumber", 1008},
472 {"ErrorMessage", "Invalid clienttransactionid: must be a uint32 number"}
473 };
474 res.set_content(response.dump(), "application/json");
475 return false;
476 }
477 clientTransactionID = static_cast<int>(value);
478 }
479 catch (const std::exception &e)
480 {
481 // Invalid format or out of range
482 DEBUGDEVICE("INDI Alpaca Server", INDI::Logger::DBG_DEBUG, "Invalid clienttransactionid format");
483
484 // Set response to 400 Bad Request
485 res.status = 400;
486 json response =
487 {
488 {"ClientTransactionID", 0},
489 {"ServerTransactionID", 0},
490 {"ErrorNumber", 1008},
491 {"ErrorMessage", "Invalid clienttransactionid: must be a uint32 number"}
492 };
493 res.set_content(response.dump(), "application/json");
494 return false;
495 }
496 }
497
498 // For server transaction ID, we'll use a static counter
499 static int transactionCounter = 1;
500 serverTransactionID = transactionCounter++;
501
502 DEBUGFDEVICE("INDI Alpaca Server", INDI::Logger::DBG_DEBUG,
503 "Client ID: %d, Transaction IDs - Client: %d, Server: %d",
504 clientID, clientTransactionID, serverTransactionID);
505
506 return true;
507}
508
509// Helper method to parse form-urlencoded data from request body
510void DeviceManager::parseFormUrlEncodedBody(const std::string &body, std::map<std::string, std::string> &params)
511{
512 std::istringstream stream(body);
513 std::string pair;
514
515 while (std::getline(stream, pair, '&'))
516 {
517 size_t pos = pair.find('=');
518 if (pos != std::string::npos)
519 {
520 std::string key = pair.substr(0, pos);
521 std::string value = pair.substr(pos + 1);
522
523 // URL decode the key and value
524 auto decode = [](const std::string & s) -> std::string
525 {
526 std::string result;
527 for (size_t i = 0; i < s.length(); ++i)
528 {
529 if (s[i] == '+')
530 {
531 result += ' ';
532 }
533 else if (s[i] == '%' && i + 2 < s.length())
534 {
535 int value;
536 std::istringstream hex_stream(s.substr(i + 1, 2));
537 if (hex_stream >> std::hex >> value)
538 {
539 result += static_cast<char>(value);
540 i += 2;
541 }
542 else
543 {
544 result += s[i];
545 }
546 }
547 else
548 {
549 result += s[i];
550 }
551 }
552 return result;
553 };
554
555 params[decode(key)] = decode(value);
556 }
557 }
558}
559
561{
562 DEBUGFDEVICE("INDI Alpaca Server", INDI::Logger::DBG_DEBUG, "Handling setup request: %s", req.path.c_str());
563
564 // Parse path to determine device type and number
565 std::string path = req.path;
566
567 // Setup API format: /setup/v1/{deviceType}/{deviceNumber}/setup
568 if (path.find("/setup/v1/") == 0)
569 {
570 std::string setupPath = path.substr(10); // Remove "/setup/v1/"
571
572 // Parse device type, number, and method
573 std::istringstream ss(setupPath);
574 std::string deviceType, deviceNumberStr, method;
575
576 std::getline(ss, deviceType, '/');
577 std::getline(ss, deviceNumberStr, '/');
578 std::getline(ss, method, '/');
579
580 // Validate
581 if (deviceType.empty() || deviceNumberStr.empty() || method != "setup")
582 {
583 res.set_content("<html><body><h1>Invalid Setup Request</h1><p>Invalid URL format. Expected: /setup/v1/{deviceType}/{deviceNumber}/setup</p></body></html>",
584 "text/html");
585 res.status = 400; // Bad Request
586 return;
587 }
588
589 // Check if device type is lowercase
590 std::string deviceTypeLower = deviceType;
591 std::transform(deviceTypeLower.begin(), deviceTypeLower.end(), deviceTypeLower.begin(), ::tolower);
592 if (deviceType != deviceTypeLower)
593 {
594 res.set_content("<html><body><h1>Invalid Setup Request</h1><p>Device type must be lowercase</p></body></html>",
595 "text/html");
596 res.status = 400; // Bad Request
597 return;
598 }
599
600 // Convert device number
601 int deviceNumber;
602 try
603 {
604 deviceNumber = std::stoi(deviceNumberStr);
605 }
606 catch (const std::exception &e)
607 {
608 res.set_content("<html><body><h1>Invalid Setup Request</h1><p>Invalid device number</p></body></html>", "text/html");
609 res.status = 400; // Bad Request
610 return;
611 }
612
613 // Find bridge for device type and number
614 std::lock_guard<std::mutex> lock(m_Mutex);
615 auto it = m_Bridges.find({deviceType, deviceNumber});
616 if (it == m_Bridges.end())
617 {
618 res.set_content("<html><body><h1>Device Not Found</h1><p>The requested device was not found</p></body></html>",
619 "text/html");
620 res.status = 404; // Not Found
621 return;
622 }
623
624 // Generate setup page
625 std::string deviceName = it->second->getDeviceName();
626 std::string uniqueID = it->second->getUniqueID();
627
628 // Create a simple HTML setup page
629 std::stringstream html;
630 html << "<!DOCTYPE html>\n";
631 html << "<html>\n";
632 html << "<head>\n";
633 html << " <title>Alpaca Setup - " << deviceName << "</title>\n";
634 html << " <style>\n";
635 html << " body { font-family: Arial, sans-serif; margin: 20px; }\n";
636 html << " h1 { color: #333; }\n";
637 html << " .info { margin-bottom: 20px; }\n";
638 html << " .info div { margin-bottom: 5px; }\n";
639 html << " label { display: inline-block; width: 150px; font-weight: bold; }\n";
640 html << " </style>\n";
641 html << "</head>\n";
642 html << "<body>\n";
643 html << " <h1>Alpaca Device Setup</h1>\n";
644 html << " <div class=\"info\">\n";
645 html << " <div><label>Device Name:</label> " << deviceName << "</div>\n";
646 html << " <div><label>Device Type:</label> " << deviceType << "</div>\n";
647 html << " <div><label>Device Number:</label> " << deviceNumber << "</div>\n";
648 html << " <div><label>Unique ID:</label> " << uniqueID << "</div>\n";
649 html << " </div>\n";
650 html << " <p>This is a minimal setup page for the device. Additional device-specific setup options can be added here.</p>\n";
651 html << "</body>\n";
652 html << "</html>";
653
654 res.set_content(html.str(), "text/html");
655 }
656 else
657 {
658 // Unknown setup endpoint
659 res.set_content("<html><body><h1>Unknown Setup Endpoint</h1><p>The requested setup endpoint is not valid</p></body></html>",
660 "text/html");
661 res.status = 404; // Not Found
662 }
663}
664
665void DeviceManager::handleManagementRequest(const std::string &endpoint,
666 const httplib::Request &req,
668 int clientTransactionID, int serverTransactionID)
669{
670 INDI_UNUSED(req);
671 DEBUGFDEVICE("INDI Alpaca Server", INDI::Logger::DBG_DEBUG, "Handling management request: %s", endpoint.c_str());
672
673 if (endpoint == "apiversions")
674 {
675 // Return supported API versions
676 json response =
677 {
678 {"Value", json::array({1})},
679 {"ClientTransactionID", clientTransactionID},
680 {"ServerTransactionID", serverTransactionID},
681 {"ErrorNumber", 0},
682 {"ErrorMessage", ""}
683 };
684 res.set_content(response.dump(), "application/json");
685 }
686 else if (endpoint == "v1/description")
687 {
688 // Return server description
689 json response =
690 {
691 {"Value", "INDI Alpaca Server"},
692 {"ClientTransactionID", clientTransactionID},
693 {"ServerTransactionID", serverTransactionID},
694 {"ErrorNumber", 0},
695 {"ErrorMessage", ""}
696 };
697 res.set_content(response.dump(), "application/json");
698 }
699 else if (endpoint == "v1/configureddevices")
700 {
701 // Return list of configured devices
702 json devices = json::array();
703
704 std::lock_guard<std::mutex> lock(m_Mutex);
705 for (const auto &pair : m_Bridges)
706 {
707 devices.push_back(
708 {
709 {"DeviceName", pair.second->getDeviceName()},
710 {"DeviceType", pair.second->getDeviceType()},
711 {"DeviceNumber", pair.second->getDeviceNumber()},
712 {"UniqueID", pair.second->getUniqueID()}
713 });
714 }
715
716 json response =
717 {
718 {"Value", devices},
719 {"ClientTransactionID", clientTransactionID},
720 {"ServerTransactionID", serverTransactionID},
721 {"ErrorNumber", 0},
722 {"ErrorMessage", ""}
723 };
724 res.set_content(response.dump(), "application/json");
725 }
726 else
727 {
728 // Unknown management endpoint
729 json response =
730 {
731 {"ClientTransactionID", clientTransactionID},
732 {"ServerTransactionID", serverTransactionID},
733 {"ErrorNumber", 1005},
734 {"ErrorMessage", "Unknown management endpoint"}
735 };
736 res.set_content(response.dump(), "application/json");
737 res.status = 404; // Not Found
738 }
739}
740
741std::vector<AlpacaDeviceInfo> DeviceManager::getDeviceList()
742{
743 std::lock_guard<std::mutex> lock(m_Mutex);
744
745 std::vector<AlpacaDeviceInfo> devices;
746 for (const auto &pair : m_Bridges)
747 {
748 AlpacaDeviceInfo info;
749 info.deviceNumber = pair.second->getDeviceNumber();
750 info.deviceName = pair.second->getDeviceName();
751 info.deviceType = pair.second->getDeviceType();
752 info.uniqueID = pair.second->getUniqueID();
753 devices.push_back(info);
754 }
755
756 return devices;
757}
hid_device * device
void addDevice(INDI::BaseDevice device)
void handleAlpacaRequest(const httplib::Request &req, httplib::Response &res)
std::vector< AlpacaDeviceInfo > getDeviceList()
void handleSetupRequest(const httplib::Request &req, httplib::Response &res)
static void parseFormUrlEncodedBody(const std::string &body, std::map< std::string, std::string > &params)
void removeDevice(INDI::BaseDevice device)
void sendNewSwitch(const INDI::PropertySwitch &switchProperty)
bool extractTransactionIDs(const httplib::Request &req, httplib::Response &res, int &clientTransactionID, int &serverTransactionID)
static DeviceManager * getInstance()
void setAlpacaClient(const std::shared_ptr< AlpacaClient > &client)
void updateDeviceProperty(INDI::Property property)
void sendNewNumber(const INDI::PropertyNumber &numberProperty)
Class to provide basic INDI device functionality.
Definition basedevice.h:52
const char * getName() const
Provides generic container for INDI properties.
const char * getName() const
a class to store JSON values
string_t dump(const int indent=-1, const char indent_char=' ', const bool ensure_ascii=false, const error_handler_t error_handler=error_handler_t::strict) const
serialization
void push_back(basic_json &&val)
add an object to an array
#define INDI_UNUSED(x)
Definition indidevapi.h:132
#define DEBUGDEVICE(device, priority, msg)
Definition indilogger.h:60
#define DEBUGFDEVICE(device, priority, msg,...)
Definition indilogger.h:61
basic_json<> json
default specialization
std::string deviceName
std::string uniqueID
std::string deviceType
bool has_param(const std::string &key) const
Definition httplib.h:9824
std::string get_param_value(const std::string &key, size_t id=0) const
Definition httplib.h:9829
std::string path
Definition httplib.h:756
void set_content(const char *s, size_t n, const std::string &content_type)
Definition httplib.h:9995
std::string body
Definition httplib.h:825