MythTV master
firewiredevice.cpp
Go to the documentation of this file.
1
7// C++ headers
8#include <algorithm>
9#include <chrono> // for milliseconds
10#include <thread> // for sleep_for
11
12// Qt headers
13#include <QMap>
14
15// MythTV headers
16#include "libmythbase/mythconfig.h"
18
19#include "linuxfirewiredevice.h"
20#if CONFIG_FIREWIRE_OSX
22#endif
23#include "mpeg/mpegtables.h"
24
25#define LOC QString("FireDev(%1): ").arg(guid_to_string(m_guid))
26
27static void fw_init(QMap<uint64_t,QString> &id_to_model);
28
29QMap<uint64_t,QString> FirewireDevice::s_idToModel;
31
32FirewireDevice::FirewireDevice(uint64_t guid, uint subunitid, uint speed) :
33 m_guid(guid), m_subunitid(subunitid),
34 m_speed(speed)
35{
36}
37
39{
40 if (listener)
41 {
42#ifdef __cpp_lib_ranges_contains
43 if (!std::ranges::contains(m_listeners, listener))
44#else
45 auto it = std::ranges::find(m_listeners, listener);
46 if (it == m_listeners.end())
47#endif
48 m_listeners.push_back(listener);
49 }
50
51 LOG(VB_RECORD, LOG_INFO, LOC +
52 QString("AddListener() %1").arg(m_listeners.size()));
53}
54
56{
57 auto it = std::ranges::find(m_listeners, listener);
58 while (it != m_listeners.end())
59 {
60 it = m_listeners.erase(it);
61 it = find(it, m_listeners.end(), listener);
62 }
63
64 LOG(VB_RECORD, LOG_INFO, LOC +
65 QString("RemoveListener() %1").arg(m_listeners.size()));
66}
67
69{
70 QMutexLocker locker(&m_lock);
71
72 std::vector<uint8_t> cmd;
73 std::vector<uint8_t> ret;
74
75 cmd.push_back(kAVCControlCommand);
77 cmd.push_back(kAVCUnitPowerOpcode);
78 cmd.push_back((on) ? kAVCPowerStateOn : kAVCPowerStateOff);
79
80 QString cmdStr = (on) ? "on" : "off";
81 LOG(VB_RECORD, LOG_INFO, LOC + QString("Powering %1").arg(cmdStr));
82
83 if (!SendAVCCommand(cmd, ret, -1))
84 {
85 LOG(VB_GENERAL, LOG_ERR, LOC + "Power on cmd failed (no response)");
86 return false;
87 }
88
89 if (kAVCAcceptedStatus != ret[0])
90 {
91 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Power %1 failed").arg(cmdStr));
92
93 return false;
94 }
95
96 LOG(VB_RECORD, LOG_INFO, LOC +
97 QString("Power %1 cmd sent successfully").arg(cmdStr));
98
99 return true;
100}
101
103{
104 QMutexLocker locker(&m_lock);
105
106 std::vector<uint8_t> cmd;
107 std::vector<uint8_t> ret;
108
109 cmd.push_back(kAVCStatusInquiryCommand);
111 cmd.push_back(kAVCUnitPowerOpcode);
112 cmd.push_back(kAVCPowerStateQuery);
113
114 LOG(VB_CHANNEL, LOG_INFO, LOC + "Requesting STB Power State");
115
116 if (!SendAVCCommand(cmd, ret, -1))
117 {
118 LOG(VB_GENERAL, LOG_ERR, LOC + "Power cmd failed (no response)");
120 }
121
122 QString loc = LOC + "STB Power State: ";
123
124 if (ret[0] != kAVCResponseImplemented)
125 {
126 LOG(VB_CHANNEL, LOG_INFO, loc + "Query not implemented");
127 return kAVCPowerUnknown;
128 }
129
130 // check 1st operand..
131 if (ret[3] == kAVCPowerStateOn)
132 {
133 LOG(VB_CHANNEL, LOG_INFO, loc + "On");
134 return kAVCPowerOn;
135 }
136
137 if (ret[3] == kAVCPowerStateOff)
138 {
139 LOG(VB_CHANNEL, LOG_INFO, loc + "Off");
140 return kAVCPowerOff;
141 }
142
143 LOG(VB_GENERAL, LOG_ERR, LOC + "STB Power State: Unknown Response");
144
145 return kAVCPowerUnknown;
146}
147
148bool FirewireDevice::SetChannel(const QString &panel_model,
149 uint alt_method, uint channel)
150{
151 LOG(VB_CHANNEL, LOG_INFO, QString("SetChannel(model %1, alt %2, chan %3)")
152 .arg(panel_model).arg(alt_method).arg(channel));
153
154 QMutexLocker locker(&m_lock);
155 LOG(VB_CHANNEL, LOG_INFO, "SetChannel() -- locked");
156
157 if (!IsSTBSupported(panel_model))
158 {
159 LOG(VB_GENERAL, LOG_ERR, LOC +
160 QString("Model: '%1' ").arg(panel_model) +
161 "is not supported by internal channel changer.");
162 return false;
163 }
164
165 std::array<uint,3> digit {
166 (channel % 1000) / 100,
167 (channel % 100) / 10,
168 (channel % 10)
169 };
170
172 {
173 LOG(VB_GENERAL, LOG_ERR, LOC +
174 "SetChannel: Extended subunits are not supported.");
175
176 return false;
177 }
178
179 std::vector<uint8_t> cmd;
180 std::vector<uint8_t> ret;
181
182 if ((panel_model.toUpper() == "SA GENERIC") ||
183 (panel_model.toUpper() == "SA4200HD") ||
184 (panel_model.toUpper() == "SA4250HDC"))
185 {
186 if (panel_model.toUpper() == "SA4250HDC")
187 {
188 LOG(VB_GENERAL, LOG_ERR, LOC +
189 "The Scientific Atlanta 4250 HDC is not supported "
190 "\n\t\t\tby any MythTV Firewire channel changer."
191 "At the moment you must use an IR blaster.");
192 }
193
194 cmd.push_back(kAVCControlCommand);
195 cmd.push_back(kAVCSubunitTypePanel | m_subunitid);
196 cmd.push_back(kAVCPanelPassThrough);
198
199 cmd.push_back(4); // operand length
200 cmd.push_back((channel>>8) & 0x0f);
201 cmd.push_back(channel & 0xff);
202 cmd.push_back(0x00);
203 cmd.push_back(0x00);
204
205 if (!SendAVCCommand(cmd, ret, -1))
206 return false;
207
208 bool press_ok = (kAVCAcceptedStatus == ret[0]);
209
211 if (!SendAVCCommand(cmd, ret, -1))
212 return false;
213
214 bool release_ok = (kAVCAcceptedStatus == ret[0]);
215
216 if (!press_ok && !release_ok)
217 {
218 LOG(VB_GENERAL, LOG_ERR, LOC + "Tuning failed");
219 return false;
220 }
221
222 SetLastChannel(channel);
223 return true;
224 }
225
226 // the PACE is obviously not a Motorola channel changer, but the
227 // same commands work for it as the Motorola.
228 bool is_mot = ((panel_model.startsWith("DCT-", Qt::CaseInsensitive)) ||
229 (panel_model.startsWith("DCH-", Qt::CaseInsensitive)) ||
230 (panel_model.startsWith("DCX-", Qt::CaseInsensitive)) ||
231 (panel_model.startsWith("QIP-", Qt::CaseInsensitive)) ||
232 (panel_model.startsWith("MOTO", Qt::CaseInsensitive)) ||
233 (panel_model.startsWith("PACE-", Qt::CaseInsensitive)));
234
235 if (is_mot && !alt_method)
236 {
237 for (uint d : digit)
238 {
239 cmd.clear();
240 cmd.push_back(kAVCControlCommand);
241 cmd.push_back(kAVCSubunitTypePanel | m_subunitid);
242 cmd.push_back(kAVCPanelPassThrough);
243 cmd.push_back((kAVCPanelKey0 + d) | kAVCPanelKeyPress);
244 cmd.push_back(0x00);
245 cmd.push_back(0x00);
246 cmd.push_back(0x00);
247 cmd.push_back(0x00);
248
249 if (!SendAVCCommand(cmd, ret, -1))
250 return false;
251
252 std::this_thread::sleep_for(500ms);
253 }
254
255 SetLastChannel(channel);
256 return true;
257 }
258
259 if (is_mot && alt_method)
260 {
261 cmd.push_back(kAVCControlCommand);
262 cmd.push_back(kAVCSubunitTypePanel | m_subunitid);
263 cmd.push_back(kAVCPanelPassThrough);
265
266 cmd.push_back(4); // operand length
267 cmd.push_back((channel>>8) & 0x0f);
268 cmd.push_back(channel & 0xff);
269 cmd.push_back(0x00);
270 cmd.push_back(0xff);
271
272 if (!SendAVCCommand(cmd, ret, -1))
273 return false;
274
275 SetLastChannel(channel);
276 return true;
277 }
278
279 if (panel_model.toUpper() == "SA3250HD")
280 {
281 cmd.push_back(kAVCControlCommand);
282 cmd.push_back(kAVCSubunitTypePanel | m_subunitid);
283 cmd.push_back(kAVCPanelPassThrough);
285
286 cmd.push_back(4); // operand length
287 cmd.push_back(0x30 | digit[2]);
288 cmd.push_back(0x30 | digit[1]);
289 cmd.push_back(0x30 | digit[0]);
290 cmd.push_back(0xff);
291
292 if (!SendAVCCommand(cmd, ret, -1))
293 return false;
294
295 cmd[5] = 0x30 | digit[0];
296 cmd[6] = 0x30 | digit[1];
297 cmd[7] = 0x30 | digit[2];
298
299 if (!SendAVCCommand(cmd, ret, -1))
300 return false;
301
302 SetLastChannel(channel);
303 return true;
304 }
305
306 return false;
307}
308
310 const unsigned char *data, uint dataSize)
311{
312 if ((dataSize >= TSPacket::kSize) && (data[0] == SYNC_BYTE) &&
313 ((data[1] & 0x1f) == 0) && (data[2] == 0))
314 {
315 ProcessPATPacket(*(reinterpret_cast<const TSPacket*>(data)));
316 }
317
318 for (auto & listener : m_listeners)
319 listener->AddData(data, dataSize);
320}
321
323{
324 m_bufferCleared = (channel == m_lastChannel);
325 m_lastChannel = channel;
326
327 LOG(VB_GENERAL, LOG_INFO, QString("SetLastChannel(%1): cleared: %2")
328 .arg(channel).arg(m_bufferCleared ? "yes" : "no"));
329}
330
332{
333 if (!tspacket.TransportError() && !tspacket.Scrambled() &&
334 tspacket.HasPayload() && tspacket.PayloadStart() && (tspacket.PID() == 0))
335 {
336 PSIPTable pes(tspacket);
337 uint crc = pes.CalcCRC();
338 m_bufferCleared |= (crc != m_lastCrc);
339 m_lastCrc = crc;
340#if 0
341 LOG(VB_RECORD, LOG_DEBUG, LOC +
342 QString("ProcessPATPacket: CRC 0x%1 cleared: %2")
343 .arg(crc,0,16).arg(m_bufferCleared ? "yes" : "no"));
344#endif
345 }
346 else
347 {
348 LOG(VB_GENERAL, LOG_ERR, LOC + "Can't handle large PAT's");
349 }
350}
351
352QString FirewireDevice::GetModelName(uint vendor_id, uint model_id)
353{
354 QMutexLocker locker(&s_staticLock);
355 if (s_idToModel.empty())
357
358 QString ret = s_idToModel[(((uint64_t) vendor_id) << 32) | model_id];
359
360 if (ret.isEmpty())
361 return "MOTO GENERIC";
362 return ret;
363}
364
365std::vector<AVCInfo> FirewireDevice::GetSTBList(void)
366{
367 std::vector<AVCInfo> list;
368
369#if CONFIG_FIREWIRE_LINUX
371#elif CONFIG_FIREWIRE_OSX
373#endif
374
375// #define DEBUG_AVC_INFO
376#ifdef DEBUG_AVC_INFO
378 info.m_guid = 0x0016928a7b600001ULL;
379 info.m_specid = 0x0;
380 info.m_vendorid = 0x000014f8;
381 info.m_modelid = 0x00001072;
382 info.m_firmware_revision = 0x0;
383 info.m_product_name = "Explorer 4200 HD";
384 list.push_back(info);
385
386 info.m_guid = 0xff2145a850e39810ULL;
387 info.m_specid = 0x0;
388 info.m_vendorid = 0x000014f8;
389 info.m_modelid = 0x00000be0;
390 info.m_firmware_revision = 0x0;
391 info.m_product_name = "Explorer 3250 HD";
392 list.push_back(info);
393#endif // DEBUG_AVC_INFO
394
395 return list;
396}
397
398static void fw_init(QMap<uint64_t,QString> &id_to_model)
399{
400 const std::array<const uint64_t,16> sa_vendor_ids
401 {
402 0x0a73, 0x0f21, 0x11e6, 0x14f8, 0x1692, 0x1868,
403 0x1947, 0x1ac3, 0x1bd7, 0x1cea, 0x1e6b, 0x21be,
404 0x223a, 0x22ce, 0x23be, 0x252e,
405 };
406
407 for (uint64_t vendor_id : sa_vendor_ids)
408 {
409 id_to_model[vendor_id << 32 | 0x0be0] = "SA3250HD";
410 id_to_model[vendor_id << 32 | 0x1072] = "SA4200HD";
411 id_to_model[vendor_id << 32 | 0x10cc] = "SA4250HDC";
412 id_to_model[vendor_id << 32 | 0x22ce] = "SA8300HD";
413 }
414
415 const std::array<uint64_t,59> motorola_vendor_ids
416 {
417 /* DCH-3200, DCX-3200 */
418 0x1c11, 0x1cfb, 0x1fc4, 0x23a3, 0x23ee, 0x25f1,
419 0xfa01, 0x25f1, 0x25f2, 0xcc7d37, 0x946269, 0x6455b1,
420 /* DCX-3432 */
421 0x24a0,
422 /* DCH-3416 */
423 0x1e46,
424 /* DCT-3416 */
425 0x1bdd,
426 /* DCT-3412 */
427 0x159a,
428 /* DCT-6200, DCT-3416 */
429 0x0ce5, 0x0e5c, 0x1225, 0x0f9f, 0x1180,
430 0x12c9, 0x11ae, 0x152f, 0x14e8, 0x16b5, 0x1371,
431 0x19a6, 0x1aad, 0x0b06, 0x195e, 0x10dc,
432 /* DCT-6212 */
433 0x0f9f, 0x152f,
434 /* DCT-6216, 2224 */
435 0x17ee, 0x1a66,
436 /* QIP 6200 */
437 0x211e,
438 /* QIP 7100 */
439 0x2374,
440 /* unknown, see http://standards.ieee.org/regauth/oui/oui.txt */
441 0x04db, 0x0406, 0x0ce5, 0x111a, 0x1225, 0x1404,
442 0x1626, 0x18c0, 0x1ade, 0x1cfb, 0x2040, 0x2180,
443 0x2210, 0x230b, 0x2375, 0x2395, 0x23a2, 0x23ed,
444 0x23ee, 0x23a0, 0x23a1,
445 };
446
447 for (uint64_t vendor_id : motorola_vendor_ids)
448 {
449 id_to_model[vendor_id << 32 | 0xf740] = "DCX-3200";
450 id_to_model[vendor_id << 32 | 0xf804] = "DCX-3200";
451 id_to_model[vendor_id << 32 | 0xfa03] = "DCX-3200";
452 id_to_model[vendor_id << 32 | 0xfa05] = "DCX-3200";
453 id_to_model[vendor_id << 32 | 0xfa07] = "DCX-3200";
454 id_to_model[vendor_id << 32 | 0x24a1] = "DCX-3200";
455 id_to_model[vendor_id << 32 | 0x2322] = "DCX-3200";
456 id_to_model[vendor_id << 32 | 0xea05] = "DCX-3432";
457 id_to_model[vendor_id << 32 | 0xd330] = "DCH-3200";
458 id_to_model[vendor_id << 32 | 0xb630] = "DCH-3416";
459 id_to_model[vendor_id << 32 | 0x34cb] = "DCT-3412";
460 id_to_model[vendor_id << 32 | 0x346b] = "DCT-3416";
461 id_to_model[vendor_id << 32 | 0xb630] = "DCT-3416";
462 id_to_model[vendor_id << 32 | 0x6200] = "DCT-6200";
463 id_to_model[vendor_id << 32 | 0x620a] = "DCT-6200";
464 id_to_model[vendor_id << 32 | 0x64ca] = "DCT-6212";
465 id_to_model[vendor_id << 32 | 0x64cb] = "DCT-6212";
466 id_to_model[vendor_id << 32 | 0x646b] = "DCT-6216";
467 id_to_model[vendor_id << 32 | 0x8100] = "QIP-7100";
468 id_to_model[vendor_id << 32 | 0x7100] = "QIP-6200";
469 id_to_model[vendor_id << 32 | 0x0001] = "QIP-7100";
470 }
471
472 const std::array<const uint64_t,2> pace_vendor_ids
473 {
474 /* PACE 550-HD & 779 */
475 0x1cc3, 0x5094,
476 };
477
478 for (uint64_t vendor_id : pace_vendor_ids)
479 {
480 id_to_model[vendor_id << 32 | 0x10551] = "PACE-550";
481 id_to_model[vendor_id << 32 | 0x10755] = "PACE-779";
482 }
483}
484
485bool FirewireDevice::IsSTBSupported(const QString &panel_model)
486{
487 QString model = panel_model.toUpper();
488 return ((model == "DCH-3200") ||
489 (model == "DCH-3416") ||
490 (model == "DCT-3412") ||
491 (model == "DCT-3416") ||
492 (model == "DCT-6200") ||
493 (model == "DCT-6212") ||
494 (model == "DCT-6216") ||
495 (model == "DCX-3200") ||
496 (model == "SA3250HD") ||
497 (model == "SA4200HD") ||
498 (model == "SA4250HDC") ||
499 (model == "SA8300HD") ||
500 (model == "PACE-550") ||
501 (model == "PACE-779") ||
502 (model == "QIP-6200") ||
503 (model == "QIP-7100") ||
504 (model == "SA GENERIC") ||
505 (model == "MOTO GENERIC"));
506}
static std::vector< AVCInfo > GetSTBList(void)
virtual void AddListener(TSDataListener *listener)
void ProcessPATPacket(const TSPacket &tspacket)
static QMap< uint64_t, QString > s_idToModel
Vendor ID + Model ID to FirewireDevice STB model string.
virtual void RemoveListener(TSDataListener *listener)
void SetLastChannel(uint channel)
static QMutex s_staticLock
FirewireDevice(uint64_t guid, uint subunitid, uint speed)
virtual PowerState GetPowerState(void)
static std::vector< AVCInfo > GetSTBList(void)
std::vector< TSDataListener * > m_listeners
virtual bool SendAVCCommand(const std::vector< uint8_t > &cmd, std::vector< uint8_t > &result, int retry_cnt)=0
virtual bool SetChannel(const QString &panel_model, uint alt_method, uint channel)
static bool IsSTBSupported(const QString &model)
static QString GetModelName(uint vendor_id, uint model_id)
virtual bool SetPowerState(bool on)
virtual void BroadcastToListeners(const unsigned char *data, uint dataSize)
static std::vector< AVCInfo > GetSTBList(void)
uint CalcCRC(void) const
Definition: pespacket.cpp:174
A PSIP table is a variant of a PES packet containing an MPEG, ATSC or DVB table.
Definition: mpegtables.h:410
unsigned int PID(void) const
Definition: tspacket.h:93
bool HasPayload(void) const
Definition: tspacket.h:116
bool PayloadStart(void) const
Definition: tspacket.h:89
bool TransportError(void) const
Definition: tspacket.h:86
bool Scrambled(void) const
Definition: tspacket.h:112
Used to access the data of a Transport Stream packet.
Definition: tspacket.h:208
static constexpr unsigned int kSize
Definition: tspacket.h:261
unsigned int uint
Definition: compat.h:60
static pid_list_t::iterator find(const PIDInfoMap &map, pid_list_t &list, pid_list_t::iterator begin, pid_list_t::iterator end, bool find_open)
#define LOC
FirewireDevice Copyright (c) 2005 by Jim Westfall Distributed as part of MythTV under GPL v2 and late...
static void fw_init(QMap< uint64_t, QString > &id_to_model)
static const iso6937table * d
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
dictionary info
Definition: azlyrics.py:7
static constexpr uint8_t SYNC_BYTE
Definition: tspacket.h:21