35#ifdef _USE_SYSTEM_JSONLIB
36#include <nlohmann/json.hpp>
50DeviceManager::DeviceManager()
71 m_Client->sendNewNumber(numberProperty);
84 m_Client->sendNewSwitch(switchProperty);
94 std::lock_guard<std::mutex> lock(m_Mutex);
96 const char *deviceName =
device.getDeviceName();
100 if (m_Devices.find(deviceName) != m_Devices.end())
103 m_Devices[deviceName] =
device;
105 createBridgeIfReady(
device);
110 const char *deviceName =
device.getDeviceName();
113 if (m_DeviceNumberMap.find(deviceName) != m_DeviceNumberMap.end())
117 uint32_t
interface = device.getDriverInterface();
121 "Device %s not ready yet (interface = 0), waiting for driver info", deviceName);
125 std::string deviceType = getAlpacaDeviceType(interface);
126 if (deviceType.empty())
134 int deviceNumber = 0;
135 while (m_Bridges.find({deviceType, deviceNumber}) != m_Bridges.end())
138 auto bridge = createBridge(
device, deviceNumber);
145 m_Bridges[{deviceType, deviceNumber}] = std::move(bridge);
146 m_DeviceNumberMap[deviceName] = {deviceType, deviceNumber};
148 deviceType.c_str(), deviceName, deviceNumber);
153 std::lock_guard<std::mutex> lock(m_Mutex);
155 const char *deviceName =
device.getDeviceName();
159 auto it = m_DeviceNumberMap.find(deviceName);
160 if (it == m_DeviceNumberMap.end())
167 BridgeKey key = it->second;
168 m_Bridges.erase(key);
169 m_DeviceNumberMap.erase(it);
170 m_Devices.erase(deviceName);
173 deviceName, key.second);
178 std::lock_guard<std::mutex> lock(m_Mutex);
180 const char *deviceName =
property.getDeviceName();
185 if (strcmp(property.
getName(),
"DRIVER_INFO") == 0)
188 auto deviceIt = m_Devices.find(deviceName);
189 if (deviceIt != m_Devices.end())
190 createBridgeIfReady(deviceIt->second);
194 auto it = m_DeviceNumberMap.find(deviceName);
195 if (it == m_DeviceNumberMap.end())
202 auto bridgeIt = m_Bridges.find(it->second);
203 if (bridgeIt != m_Bridges.end())
205 bridgeIt->second->updateProperty(property);
209std::string DeviceManager::getAlpacaDeviceType(uint32_t interface)
225 std::string deviceType = getAlpacaDeviceType(
device.getDriverInterface());
228 if (deviceType ==
"telescope")
232 return std::make_unique<TelescopeBridge>(
device, deviceNumber);
234 else if (deviceType ==
"camera")
238 return std::make_unique<CameraBridge>(
device, deviceNumber);
255 int clientTransactionID, serverTransactionID;
263 std::string path = req.
path;
266 if (path.find(
"/management/") == 0)
268 handleManagementRequest(path.substr(12), req, res, clientTransactionID, serverTransactionID);
273 if (path.find(
"/api/v1/") == 0)
275 std::string apiPath = path.substr(8);
278 std::istringstream ss(apiPath);
279 std::string deviceType, deviceNumberStr, method;
281 std::getline(ss, deviceType,
'/');
282 std::getline(ss, deviceNumberStr,
'/');
283 std::getline(ss, method,
'/');
286 if (deviceType.empty() || deviceNumberStr.empty() || method.empty())
290 {
"ClientTransactionID", clientTransactionID},
291 {
"ServerTransactionID", serverTransactionID},
292 {
"ErrorNumber", 1001},
293 {
"ErrorMessage",
"Invalid API request format"}
301 std::string deviceTypeLower = deviceType;
302 std::transform(deviceTypeLower.begin(), deviceTypeLower.end(), deviceTypeLower.begin(), ::tolower);
303 if (deviceType != deviceTypeLower)
307 {
"ClientTransactionID", clientTransactionID},
308 {
"ServerTransactionID", serverTransactionID},
309 {
"ErrorNumber", 1007},
310 {
"ErrorMessage",
"Device type must be lowercase"}
321 deviceNumber = std::stoi(deviceNumberStr);
323 catch (
const std::exception &e)
327 {
"ClientTransactionID", clientTransactionID},
328 {
"ServerTransactionID", serverTransactionID},
329 {
"ErrorNumber", 1002},
330 {
"ErrorMessage",
"Invalid device number"}
338 routeRequest(deviceNumber, deviceType, method, req, res, clientTransactionID, serverTransactionID);
345 {
"ClientTransactionID", clientTransactionID},
346 {
"ServerTransactionID", serverTransactionID},
347 {
"ErrorNumber", 1000},
348 {
"ErrorMessage",
"Unknown API endpoint"}
354void DeviceManager::routeRequest(
int deviceNumber,
const std::string &deviceType,
355 const std::string &method,
357 int clientTransactionID,
int serverTransactionID)
359 std::lock_guard<std::mutex> lock(m_Mutex);
362 auto it = m_Bridges.find({deviceType, deviceNumber});
363 if (it == m_Bridges.end())
367 {
"ClientTransactionID", clientTransactionID},
368 {
"ServerTransactionID", serverTransactionID},
369 {
"ErrorNumber", 1003},
370 {
"ErrorMessage",
"Device not found"}
378 it->second->handleRequest(method, req, res);
383 auto responseJson = json::parse(res.
body);
384 responseJson[
"ClientTransactionID"] = clientTransactionID;
385 responseJson[
"ServerTransactionID"] = serverTransactionID;
386 res.
set_content(responseJson.dump(),
"application/json");
388 catch (
const std::exception &e)
392 "Failed to parse bridge response JSON, creating new response with transaction IDs");
396 {
"ClientTransactionID", clientTransactionID},
397 {
"ServerTransactionID", serverTransactionID},
398 {
"ErrorNumber", 1006},
399 {
"ErrorMessage",
"Internal server error: Invalid response format"}
407 int &serverTransactionID)
410 clientTransactionID = 0;
411 serverTransactionID = 0;
417 auto getParamValueCaseInsensitive = [&req](
const std::string & paramName) -> std::string
424 std::string lowerParamName = paramName;
425 std::transform(lowerParamName.begin(), lowerParamName.end(), lowerParamName.begin(), ::tolower);
427 for (
const auto ¶m : req.
params)
429 std::string lowerKey = param.first;
430 std::transform(lowerKey.begin(), lowerKey.end(), lowerKey.begin(), ::tolower);
431 if (lowerKey == lowerParamName)
439 std::string clientIdStr = getParamValueCaseInsensitive(
"clientid");
440 if (!clientIdStr.empty())
444 clientID = std::stoi(clientIdStr);
446 catch (
const std::exception &e)
453 std::string clientTransactionIdStr = getParamValueCaseInsensitive(
"clienttransactionid");
454 if (!clientTransactionIdStr.empty())
459 unsigned long value = std::stoul(clientTransactionIdStr);
460 if (value > UINT32_MAX)
469 {
"ClientTransactionID", 0},
470 {
"ServerTransactionID", 0},
471 {
"ErrorNumber", 1008},
472 {
"ErrorMessage",
"Invalid clienttransactionid: must be a uint32 number"}
477 clientTransactionID =
static_cast<int>(value);
479 catch (
const std::exception &e)
488 {
"ClientTransactionID", 0},
489 {
"ServerTransactionID", 0},
490 {
"ErrorNumber", 1008},
491 {
"ErrorMessage",
"Invalid clienttransactionid: must be a uint32 number"}
499 static int transactionCounter = 1;
500 serverTransactionID = transactionCounter++;
503 "Client ID: %d, Transaction IDs - Client: %d, Server: %d",
504 clientID, clientTransactionID, serverTransactionID);
512 std::istringstream stream(body);
515 while (std::getline(stream, pair,
'&'))
517 size_t pos = pair.find(
'=');
518 if (pos != std::string::npos)
520 std::string key = pair.substr(0, pos);
521 std::string value = pair.substr(pos + 1);
524 auto decode = [](
const std::string & s) -> std::string
527 for (
size_t i = 0; i < s.length(); ++i)
533 else if (s[i] ==
'%' && i + 2 < s.length())
536 std::istringstream hex_stream(s.substr(i + 1, 2));
537 if (hex_stream >> std::hex >> value)
539 result +=
static_cast<char>(value);
555 params[decode(key)] = decode(value);
565 std::string path = req.
path;
568 if (path.find(
"/setup/v1/") == 0)
570 std::string setupPath = path.substr(10);
573 std::istringstream ss(setupPath);
574 std::string deviceType, deviceNumberStr, method;
576 std::getline(ss, deviceType,
'/');
577 std::getline(ss, deviceNumberStr,
'/');
578 std::getline(ss, method,
'/');
581 if (deviceType.empty() || deviceNumberStr.empty() || method !=
"setup")
583 res.
set_content(
"<html><body><h1>Invalid Setup Request</h1><p>Invalid URL format. Expected: /setup/v1/{deviceType}/{deviceNumber}/setup</p></body></html>",
590 std::string deviceTypeLower = deviceType;
591 std::transform(deviceTypeLower.begin(), deviceTypeLower.end(), deviceTypeLower.begin(), ::tolower);
592 if (deviceType != deviceTypeLower)
594 res.
set_content(
"<html><body><h1>Invalid Setup Request</h1><p>Device type must be lowercase</p></body></html>",
604 deviceNumber = std::stoi(deviceNumberStr);
606 catch (
const std::exception &e)
608 res.
set_content(
"<html><body><h1>Invalid Setup Request</h1><p>Invalid device number</p></body></html>",
"text/html");
614 std::lock_guard<std::mutex> lock(m_Mutex);
615 auto it = m_Bridges.find({deviceType, deviceNumber});
616 if (it == m_Bridges.end())
618 res.
set_content(
"<html><body><h1>Device Not Found</h1><p>The requested device was not found</p></body></html>",
625 std::string deviceName = it->second->getDeviceName();
626 std::string uniqueID = it->second->getUniqueID();
629 std::stringstream html;
630 html <<
"<!DOCTYPE html>\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";
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";
650 html <<
" <p>This is a minimal setup page for the device. Additional device-specific setup options can be added here.</p>\n";
659 res.
set_content(
"<html><body><h1>Unknown Setup Endpoint</h1><p>The requested setup endpoint is not valid</p></body></html>",
665void DeviceManager::handleManagementRequest(
const std::string &endpoint,
668 int clientTransactionID,
int serverTransactionID)
673 if (endpoint ==
"apiversions")
678 {
"Value", json::array({1})},
679 {
"ClientTransactionID", clientTransactionID},
680 {
"ServerTransactionID", serverTransactionID},
686 else if (endpoint ==
"v1/description")
691 {
"Value",
"INDI Alpaca Server"},
692 {
"ClientTransactionID", clientTransactionID},
693 {
"ServerTransactionID", serverTransactionID},
699 else if (endpoint ==
"v1/configureddevices")
702 json devices = json::array();
704 std::lock_guard<std::mutex> lock(m_Mutex);
705 for (
const auto &pair : m_Bridges)
709 {
"DeviceName", pair.second->getDeviceName()},
710 {
"DeviceType", pair.second->getDeviceType()},
711 {
"DeviceNumber", pair.second->getDeviceNumber()},
712 {
"UniqueID", pair.second->getUniqueID()}
719 {
"ClientTransactionID", clientTransactionID},
720 {
"ServerTransactionID", serverTransactionID},
731 {
"ClientTransactionID", clientTransactionID},
732 {
"ServerTransactionID", serverTransactionID},
733 {
"ErrorNumber", 1005},
734 {
"ErrorMessage",
"Unknown management endpoint"}
743 std::lock_guard<std::mutex> lock(m_Mutex);
745 std::vector<AlpacaDeviceInfo> devices;
746 for (
const auto &pair : m_Bridges)
750 info.
deviceName = pair.second->getDeviceName();
751 info.
deviceType = pair.second->getDeviceType();
752 info.
uniqueID = pair.second->getUniqueID();
753 devices.push_back(info);
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 > ¶ms)
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.
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 DEBUGDEVICE(device, priority, msg)
#define DEBUGFDEVICE(device, priority, msg,...)
basic_json<> json
default specialization
bool has_param(const std::string &key) const
std::string get_param_value(const std::string &key, size_t id=0) const
void set_content(const char *s, size_t n, const std::string &content_type)