Instrument Neutral Distributed Interface INDI 2.2.5
hitechweather.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2025 Jasem Mutlaq. All rights reserved.
3
4 INDI HiTech Weather Station Driver
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 "hitechweather.h"
26
27#include <memory>
28#include <cstring>
29#include <cmath>
30#include <algorithm>
31
32// We declare an auto pointer to HitechWeather.
33std::unique_ptr<HitechWeather> hitechWeather(new HitechWeather());
34
40
42{
43 if (m_hidHandle)
44 {
45 hid_close(m_hidHandle);
46 m_hidHandle = nullptr;
47 }
48}
49
51{
52 return "HiTech Weather";
53}
54
56{
57 // Initialize HID library
58 if (hid_init() != 0)
59 {
60 LOG_ERROR("Failed to initialize HID library");
61 return false;
62 }
63
64 // Open HiTech Weather device
65 m_hidHandle = hid_open(HITECH_VID, HITECH_PID, nullptr);
66 if (!m_hidHandle)
67 {
68 LOGF_ERROR("Unable to open HiTech Weather device (VID: 0x%04X, PID: 0x%04X). "
69 "Please check device is connected and permissions are correct.",
70 HITECH_VID, HITECH_PID);
71 hid_exit();
72 return false;
73 }
74
75 // Get device information
76 wchar_t wstr[256];
77 if (hid_get_manufacturer_string(m_hidHandle, wstr, 256) == 0)
78 {
79 char manufacturer[256];
80 wcstombs(manufacturer, wstr, 256);
81 LOGF_INFO("Manufacturer: %s", manufacturer);
82 }
83
84 if (hid_get_product_string(m_hidHandle, wstr, 256) == 0)
85 {
86 char product[256];
87 wcstombs(product, wstr, 256);
88 LOGF_INFO("Product: %s", product);
89 }
90
91 LOG_INFO("HiTech Weather connected successfully");
92 return true;
93}
94
96{
97 if (m_hidHandle)
98 {
99 hid_close(m_hidHandle);
100 m_hidHandle = nullptr;
101 }
102 hid_exit();
103
104 LOG_INFO("HiTech Weather disconnected");
105 return true;
106}
107
109{
111
112 // Ambient temperature (standard INDI parameter)
113 addParameter("WEATHER_TEMPERATURE", "Temperature (C)", -30, 50, 15);
114
115 // Sky temperature (informational)
116 addParameter("WEATHER_SKY_TEMPERATURE", "Sky Temp (C)", -50, 30, 15);
117
118 // Cloud cover (critical parameter)
119 // 0-15% = OK (clear), 15-30% = WARNING (some clouds), 30-100% = ALERT (cloudy)
120 addParameter("WEATHER_CLOUD_COVER", "Clouds (%)", 0, 100, 15);
121 setCriticalParameter("WEATHER_CLOUD_COVER");
122
124 return true;
125}
126
128{
129 double skyTemp = 0;
130 double ambientTemp = 0;
131
132 // Read sky temperature
133 if (!getSkyTemperature(skyTemp))
134 {
135 LOG_ERROR("Failed to read sky temperature");
136 return IPS_ALERT;
137 }
138
139 // Read ambient temperature
140 if (!getAmbientTemperature(ambientTemp))
141 {
142 LOG_ERROR("Failed to read ambient temperature");
143 return IPS_ALERT;
144 }
145
146 // Calculate cloud cover from temperature differential
147 double cloudCover = calculateCloudCover(ambientTemp, skyTemp);
148
149 // Update parameters
150 setParameterValue("WEATHER_TEMPERATURE", ambientTemp);
151 setParameterValue("WEATHER_SKY_TEMPERATURE", skyTemp);
152 setParameterValue("WEATHER_CLOUD_COVER", cloudCover);
153
154 LOGF_DEBUG("Sky: %.2f°C, Ambient: %.2f°C, Cloud Cover: %.1f%%",
155 skyTemp, ambientTemp, cloudCover);
156
157 return IPS_OK;
158}
159
160bool HitechWeather::getSkyTemperature(double &skyTemp)
161{
162 if (!m_hidHandle)
163 return false;
164
165 // Prepare command: getSkyTemp
166 unsigned char cmd[8] = {CMD_GET_SKY_TEMP, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
167
168 // Send command
169 int res = hid_write(m_hidHandle, cmd, sizeof(cmd));
170 if (res < 0)
171 {
172 LOGF_ERROR("Failed to write getSkyTemp command: %ls", hid_error(m_hidHandle));
173 return false;
174 }
175
176 // Read response
177 unsigned char buf[8];
178 res = hid_read_timeout(m_hidHandle, buf, sizeof(buf), 1000);
179 if (res < 0)
180 {
181 LOGF_ERROR("Failed to read getSkyTemp response: %ls", hid_error(m_hidHandle));
182 return false;
183 }
184
185 if (res < 3)
186 {
187 LOGF_ERROR("Insufficient data received for getSkyTemp: %d bytes", res);
188 return false;
189 }
190
191 // Verify response signature (byte 3 should be 0x21)
192 if (buf[2] != 0x21)
193 {
194 LOGF_ERROR("Invalid getSkyTemp response signature: 0x%02X", buf[2]);
195 return false;
196 }
197
198 // Parse temperature
199 // Formula: skytemp = byte1 * 256 + byte2; skytemp = skytemp * 0.02 - 273.7
200 uint16_t rawTemp = (buf[0] * 256) + buf[1];
201 skyTemp = (rawTemp * 0.02) - 273.7;
202
203 return true;
204}
205
206bool HitechWeather::getAmbientTemperature(double &ambientTemp)
207{
208 if (!m_hidHandle)
209 return false;
210
211 // Prepare command: getAmb
212 unsigned char cmd[8] = {CMD_GET_AMBIENT, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
213
214 // Send command
215 int res = hid_write(m_hidHandle, cmd, sizeof(cmd));
216 if (res < 0)
217 {
218 LOGF_ERROR("Failed to write getAmb command: %ls", hid_error(m_hidHandle));
219 return false;
220 }
221
222 // Read response
223 unsigned char buf[8];
224 res = hid_read_timeout(m_hidHandle, buf, sizeof(buf), 1000);
225 if (res < 0)
226 {
227 LOGF_ERROR("Failed to read getAmb response: %ls", hid_error(m_hidHandle));
228 return false;
229 }
230
231 if (res < 5)
232 {
233 LOGF_ERROR("Insufficient data received for getAmb: %d bytes", res);
234 return false;
235 }
236
237 // Verify response signature (byte 1 should be 0x64, byte 5 should be 0x21)
238 if (buf[0] != 0x64 || buf[4] != 0x21)
239 {
240 LOGF_ERROR("Invalid getAmb response signature: 0x%02X 0x%02X", buf[0], buf[4]);
241 return false;
242 }
243
244 // Parse temperature
245 // Formula: temp = ((byte2 * 256) + byte1) / 100.0
246 // If sign byte (byte 4) = 0x10, then temperature is negative
247 uint16_t rawTemp = (buf[2] * 256) + buf[1];
248 ambientTemp = rawTemp / 100.0;
249
250 if (buf[3] == 0x10)
251 {
252 ambientTemp *= -1.0;
253 }
254
255 return true;
256}
257
258double HitechWeather::calculateCloudCover(double ambientTemp, double skyTemp)
259{
260 // Calculate temperature differential
261 double hitecDelta = ambientTemp - skyTemp;
262 double absDelta = std::abs(hitecDelta);
263
264 // Convert to cloud cover percentage
265 // Clear sky: large delta (sky much colder) -> low cloud cover
266 // Cloudy sky: small delta (clouds radiate heat) -> high cloud cover
267 // Maximum delta of 22°C normalized to 0-100% range
268 double cloudCover = 100.0 * (1.0 - std::min(absDelta, 22.0) / 22.0);
269
270 // Clamp to 0-100 range
271 cloudCover = std::max(0.0, std::min(100.0, cloudCover));
272
273 return cloudCover;
274}
bool Disconnect() override
Disconnect from device.
virtual ~HitechWeather()
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
virtual IPState updateWeather() override
updateWeather Update weather conditions from device or service. The function should not change the st...
bool Connect() override
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
const char * getDefaultName() override
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
void addDebugControl()
Add Debug control to the driver.
void addParameter(std::string name, std::string label, double numMinOk, double numMaxOk, double percWarning, bool flipWarning=false)
Add a physical weather measurable parameter to the weather driver.
bool setCriticalParameter(std::string name)
setCriticalParameter Set parameter that is considered critical to the operation of the observatory....
void setParameterValue(std::string name, double value)
setParameterValue Update weather parameter value
void setWeatherConnection(const uint8_t &value)
setWeatherConnection Set Weather connection mode. Child class should call this in the constructor bef...
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number)
Open a HID device using a Vendor ID (VID), Product ID (PID) and optionally a serial number.
Definition hid_libusb.c:612
int HID_API_EXPORT hid_init(void)
Initialize the HIDAPI library.
Definition hid_libusb.c:388
int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds)
Read an Input report from a HID device with timeout.
Definition hid_libusb.c:997
int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length)
Write an Output report to a HID device.
Definition hid_libusb.c:929
int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
Get The Product String from a HID device.
int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)
Get The Manufacturer String from a HID device.
void HID_API_EXPORT hid_close(hid_device *dev)
Close a HID device.
HID_API_EXPORT const wchar_t *HID_API_CALL hid_error(hid_device *dev)
Get a string describing the last error which occurred.
int HID_API_EXPORT hid_exit(void)
Finalize the HIDAPI library.
Definition hid_libusb.c:407
std::unique_ptr< HitechWeather > hitechWeather(new HitechWeather())
IPState
Property state.
Definition indiapi.h:158
@ IPS_ALERT
Definition indiapi.h:162
@ IPS_OK
Definition indiapi.h:160
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#define LOGF_DEBUG(fmt,...)
Definition indilogger.h:83
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition indilogger.h:72
#define LOGF_ERROR(fmt,...)
Definition indilogger.h:80
#define LOG_INFO(txt)
Definition indilogger.h:74
uint8_t cmd[4]
Definition pwc-ioctl.h:2