pyxcp 0.23.8__cp313-cp313-macosx_11_0_arm64.whl → 0.25.7__cp313-cp313-macosx_11_0_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyxcp/__init__.py +1 -1
- pyxcp/cmdline.py +14 -29
- pyxcp/config/__init__.py +1257 -1258
- pyxcp/cpp_ext/aligned_buffer.hpp +168 -0
- pyxcp/cpp_ext/bin.hpp +7 -6
- pyxcp/cpp_ext/cpp_ext.cpython-310-darwin.so +0 -0
- pyxcp/cpp_ext/cpp_ext.cpython-311-darwin.so +0 -0
- pyxcp/cpp_ext/cpp_ext.cpython-312-darwin.so +0 -0
- pyxcp/cpp_ext/cpp_ext.cpython-313-darwin.so +0 -0
- pyxcp/cpp_ext/daqlist.hpp +241 -73
- pyxcp/cpp_ext/extension_wrapper.cpp +123 -15
- pyxcp/cpp_ext/framing.hpp +360 -0
- pyxcp/cpp_ext/helper.hpp +280 -280
- pyxcp/cpp_ext/mcobject.hpp +248 -246
- pyxcp/cpp_ext/sxi_framing.hpp +332 -0
- pyxcp/daq_stim/__init__.py +145 -67
- pyxcp/daq_stim/optimize/binpacking.py +2 -2
- pyxcp/daq_stim/scheduler.cpp +8 -8
- pyxcp/errormatrix.py +2 -2
- pyxcp/examples/run_daq.py +5 -4
- pyxcp/examples/xcp_policy.py +6 -6
- pyxcp/examples/xcp_read_benchmark.py +2 -2
- pyxcp/examples/xcp_skel.py +1 -2
- pyxcp/examples/xcp_unlock.py +10 -12
- pyxcp/examples/xcp_user_supplied_driver.py +1 -2
- pyxcp/examples/xcphello.py +2 -15
- pyxcp/examples/xcphello_recorder.py +2 -2
- pyxcp/master/__init__.py +1 -0
- pyxcp/master/errorhandler.py +134 -4
- pyxcp/master/master.py +823 -252
- pyxcp/recorder/.idea/.gitignore +8 -0
- pyxcp/recorder/.idea/misc.xml +4 -0
- pyxcp/recorder/.idea/modules.xml +8 -0
- pyxcp/recorder/.idea/recorder.iml +6 -0
- pyxcp/recorder/.idea/sonarlint/issuestore/3/8/3808afc69ac1edb9d760000a2f137335b1b99728 +7 -0
- pyxcp/recorder/.idea/sonarlint/issuestore/9/a/9a2aa4db38d3115ed60da621e012c0efc0172aae +0 -0
- pyxcp/recorder/.idea/sonarlint/issuestore/b/4/b49006702b459496a8e8c94ebe60947108361b91 +0 -0
- pyxcp/recorder/.idea/sonarlint/issuestore/index.pb +7 -0
- pyxcp/recorder/.idea/sonarlint/securityhotspotstore/3/8/3808afc69ac1edb9d760000a2f137335b1b99728 +0 -0
- pyxcp/recorder/.idea/sonarlint/securityhotspotstore/9/a/9a2aa4db38d3115ed60da621e012c0efc0172aae +0 -0
- pyxcp/recorder/.idea/sonarlint/securityhotspotstore/b/4/b49006702b459496a8e8c94ebe60947108361b91 +0 -0
- pyxcp/recorder/.idea/sonarlint/securityhotspotstore/index.pb +7 -0
- pyxcp/recorder/.idea/vcs.xml +10 -0
- pyxcp/recorder/__init__.py +96 -98
- pyxcp/recorder/converter/__init__.py +4 -10
- pyxcp/recorder/reader.hpp +138 -139
- pyxcp/recorder/reco.py +1 -0
- pyxcp/recorder/rekorder.cpython-310-darwin.so +0 -0
- pyxcp/recorder/rekorder.cpython-311-darwin.so +0 -0
- pyxcp/recorder/rekorder.cpython-312-darwin.so +0 -0
- pyxcp/recorder/rekorder.cpython-313-darwin.so +0 -0
- pyxcp/recorder/rekorder.hpp +274 -274
- pyxcp/recorder/unfolder.hpp +1354 -1319
- pyxcp/recorder/wrap.cpp +184 -183
- pyxcp/recorder/writer.hpp +302 -302
- pyxcp/scripts/xcp_daq_recorder.py +54 -0
- pyxcp/scripts/xcp_fetch_a2l.py +2 -2
- pyxcp/scripts/xcp_id_scanner.py +1 -2
- pyxcp/scripts/xcp_info.py +66 -51
- pyxcp/scripts/xcp_profile.py +1 -2
- pyxcp/tests/test_daq.py +1 -1
- pyxcp/tests/test_framing.py +262 -0
- pyxcp/tests/test_master.py +210 -100
- pyxcp/tests/test_transport.py +138 -42
- pyxcp/timing.py +1 -1
- pyxcp/transport/__init__.py +8 -5
- pyxcp/transport/base.py +70 -180
- pyxcp/transport/can.py +58 -7
- pyxcp/transport/eth.py +32 -15
- pyxcp/transport/hdf5_policy.py +167 -0
- pyxcp/transport/sxi.py +126 -52
- pyxcp/transport/transport_ext.cpython-310-darwin.so +0 -0
- pyxcp/transport/transport_ext.cpython-311-darwin.so +0 -0
- pyxcp/transport/transport_ext.cpython-312-darwin.so +0 -0
- pyxcp/transport/transport_ext.cpython-313-darwin.so +0 -0
- pyxcp/transport/transport_ext.hpp +214 -0
- pyxcp/transport/transport_wrapper.cpp +249 -0
- pyxcp/transport/usb_transport.py +47 -31
- pyxcp/types.py +0 -13
- pyxcp/{utils.py → utils/__init__.py} +1 -2
- pyxcp/utils/cli.py +78 -0
- {pyxcp-0.23.8.dist-info → pyxcp-0.25.7.dist-info}/METADATA +4 -2
- pyxcp-0.25.7.dist-info/RECORD +158 -0
- {pyxcp-0.23.8.dist-info → pyxcp-0.25.7.dist-info}/WHEEL +1 -1
- pyxcp/examples/conf_sxi.json +0 -9
- pyxcp/examples/conf_sxi.toml +0 -7
- pyxcp-0.23.8.dist-info/RECORD +0 -135
- {pyxcp-0.23.8.dist-info → pyxcp-0.25.7.dist-info}/entry_points.txt +0 -0
- {pyxcp-0.23.8.dist-info → pyxcp-0.25.7.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
|
|
2
|
+
#include <cstdint>
|
|
3
|
+
|
|
4
|
+
#include <bit>
|
|
5
|
+
#include <optional>
|
|
6
|
+
#include <iostream>
|
|
7
|
+
#include <map>
|
|
8
|
+
#include <set>
|
|
9
|
+
#include <tuple>
|
|
10
|
+
#include <vector>
|
|
11
|
+
|
|
12
|
+
#include "rekorder.hpp"
|
|
13
|
+
|
|
14
|
+
const std::map<FrameCategory, std::string> FrameCategoryName {
|
|
15
|
+
{FrameCategory::META, "METADATA"},
|
|
16
|
+
{FrameCategory::CMD, "CMD"},
|
|
17
|
+
{FrameCategory::RES, "RESPONSE"},
|
|
18
|
+
{FrameCategory::ERR, "ERROR"},
|
|
19
|
+
{FrameCategory::EV, "EVENT"},
|
|
20
|
+
{FrameCategory::SERV, "SERV"},
|
|
21
|
+
{FrameCategory::DAQ, "DAQ"},
|
|
22
|
+
{FrameCategory::STIM, "STIM"},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/*
|
|
26
|
+
Base class for all frame acquisition policies.
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
---------
|
|
30
|
+
filter_out: set or None
|
|
31
|
+
A set of frame types to filter out.
|
|
32
|
+
If None, all frame types are accepted for further processing.
|
|
33
|
+
|
|
34
|
+
Example: (FrameType.REQUEST, FrameType.RESPONSE, FrameType.EVENT, FrameType.SERV)
|
|
35
|
+
==> care only about DAQ frames.
|
|
36
|
+
*/
|
|
37
|
+
class FrameAcquisitionPolicy {
|
|
38
|
+
public:
|
|
39
|
+
|
|
40
|
+
using payload_t = std::string;
|
|
41
|
+
using filter_t = std::set<FrameCategory>;
|
|
42
|
+
using frame_t = std::tuple<std::uint32_t, std::uint64_t, const payload_t>;
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
FrameAcquisitionPolicy(const std::optional<filter_t>& filter_out) {
|
|
46
|
+
if (!filter_out) {
|
|
47
|
+
m_filter_out = filter_t{};
|
|
48
|
+
} else {
|
|
49
|
+
m_filter_out = filter_out;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
std::optional<filter_t> get_filtered_out() const {
|
|
54
|
+
return m_filter_out;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
FrameAcquisitionPolicy(const FrameAcquisitionPolicy&) = delete;
|
|
58
|
+
FrameAcquisitionPolicy(FrameAcquisitionPolicy&&) = delete;
|
|
59
|
+
FrameAcquisitionPolicy() = delete;
|
|
60
|
+
|
|
61
|
+
virtual ~FrameAcquisitionPolicy() {}
|
|
62
|
+
|
|
63
|
+
virtual void feed(FrameCategory frame_category, std::uint32_t counter, std::uint64_t timestamp, const payload_t& payload) = 0;
|
|
64
|
+
|
|
65
|
+
virtual void finalize() = 0;
|
|
66
|
+
|
|
67
|
+
protected:
|
|
68
|
+
|
|
69
|
+
std::optional<filter_t> m_filter_out;
|
|
70
|
+
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
/*
|
|
75
|
+
No operation / do nothing policy.
|
|
76
|
+
*/
|
|
77
|
+
class NoOpPolicy : public FrameAcquisitionPolicy {
|
|
78
|
+
public:
|
|
79
|
+
|
|
80
|
+
NoOpPolicy(const std::optional<filter_t>& filter_out) : FrameAcquisitionPolicy(filter_out) {}
|
|
81
|
+
|
|
82
|
+
void feed(FrameCategory frame_category, std::uint32_t counter, std::uint64_t timestamp, const payload_t& payload) override {}
|
|
83
|
+
|
|
84
|
+
void finalize() override {}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
/*
|
|
89
|
+
Dequeue based frame acquisition policy.
|
|
90
|
+
|
|
91
|
+
Deprecated: Use only for compatibility reasons.
|
|
92
|
+
*/
|
|
93
|
+
class LegacyFrameAcquisitionPolicy : public FrameAcquisitionPolicy {
|
|
94
|
+
public:
|
|
95
|
+
|
|
96
|
+
using deque_t = TsQueue<frame_t>;
|
|
97
|
+
|
|
98
|
+
LegacyFrameAcquisitionPolicy(const std::optional<filter_t>& filter_out) : FrameAcquisitionPolicy(filter_out) {
|
|
99
|
+
|
|
100
|
+
m_queue_map[FrameCategory::CMD] = std::make_shared<deque_t>();
|
|
101
|
+
m_queue_map[FrameCategory::RES] = std::make_shared<deque_t>();
|
|
102
|
+
m_queue_map[FrameCategory::EV] = std::make_shared<deque_t>();
|
|
103
|
+
m_queue_map[FrameCategory::SERV] = std::make_shared<deque_t>();
|
|
104
|
+
m_queue_map[FrameCategory::DAQ] = std::make_shared<deque_t>();
|
|
105
|
+
m_queue_map[FrameCategory::META] = std::make_shared<deque_t>();
|
|
106
|
+
m_queue_map[FrameCategory::ERR] = std::make_shared<deque_t>();
|
|
107
|
+
m_queue_map[FrameCategory::STIM] = std::make_shared<deque_t>();
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
LegacyFrameAcquisitionPolicy() = delete;
|
|
112
|
+
LegacyFrameAcquisitionPolicy(const LegacyFrameAcquisitionPolicy&) = delete;
|
|
113
|
+
LegacyFrameAcquisitionPolicy(LegacyFrameAcquisitionPolicy&&) = delete;
|
|
114
|
+
|
|
115
|
+
void feed(FrameCategory frame_category, std::uint32_t counter, std::uint64_t timestamp, const payload_t& payload) override {
|
|
116
|
+
if (m_filter_out && (!(*m_filter_out).contains(frame_category))) {
|
|
117
|
+
m_queue_map[frame_category]->put({counter, timestamp, payload});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
std::shared_ptr<deque_t> get_req_queue() {
|
|
122
|
+
return m_queue_map.at(FrameCategory::CMD);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
std::shared_ptr<deque_t> get_res_queue() {
|
|
126
|
+
return m_queue_map.at(FrameCategory::RES);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
std::shared_ptr<deque_t> get_daq_queue() {
|
|
130
|
+
return m_queue_map.at(FrameCategory::DAQ);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
std::shared_ptr<deque_t> get_ev_queue() {
|
|
134
|
+
return m_queue_map.at(FrameCategory::EV);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
std::shared_ptr<deque_t> get_serv_queue() {
|
|
138
|
+
return m_queue_map.at(FrameCategory::SERV);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
std::shared_ptr<deque_t> get_meta_queue() {
|
|
142
|
+
return m_queue_map.at(FrameCategory::META);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
std::shared_ptr<deque_t> get_error_queue() {
|
|
146
|
+
return m_queue_map.at(FrameCategory::ERR);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
std::shared_ptr<deque_t> get_stim_queue() {
|
|
150
|
+
return m_queue_map.at(FrameCategory::STIM);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
void finalize() override {}
|
|
154
|
+
|
|
155
|
+
private:
|
|
156
|
+
|
|
157
|
+
std::map<FrameCategory, std::shared_ptr<deque_t>> m_queue_map{};
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
std::string hex_bytes(std::string_view payload) {
|
|
161
|
+
std::stringstream ss;
|
|
162
|
+
|
|
163
|
+
for (auto ch: payload) {
|
|
164
|
+
ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(static_cast<unsigned char>(ch)) << " ";
|
|
165
|
+
}
|
|
166
|
+
return ss.str();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/*
|
|
170
|
+
Frame acquisition policy that prints frames to stdout.
|
|
171
|
+
*/
|
|
172
|
+
class StdoutPolicy : public FrameAcquisitionPolicy {
|
|
173
|
+
public:
|
|
174
|
+
StdoutPolicy(const std::optional<filter_t>& filter_out) : FrameAcquisitionPolicy(filter_out) {}
|
|
175
|
+
|
|
176
|
+
StdoutPolicy() = delete;
|
|
177
|
+
StdoutPolicy(const StdoutPolicy&) = delete;
|
|
178
|
+
StdoutPolicy(StdoutPolicy&&) = delete;
|
|
179
|
+
|
|
180
|
+
void feed(FrameCategory frame_category, std::uint32_t counter, std::uint64_t timestamp, const payload_t& payload) override {
|
|
181
|
+
if (m_filter_out && (!(*m_filter_out).contains(frame_category))) {
|
|
182
|
+
std::cout << std::left << std::setw(8) << FrameCategoryName.at(frame_category) << " " << std::right <<
|
|
183
|
+
std::setw(6) << counter << " " << std::setw(8) << timestamp << " [ " << std::left << hex_bytes(payload) << "]" << std::endl;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
void finalize() override { }
|
|
188
|
+
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
class FrameRecorderPolicy : public FrameAcquisitionPolicy {
|
|
192
|
+
public:
|
|
193
|
+
|
|
194
|
+
FrameRecorderPolicy(const std::string& file_name, const std::optional<filter_t>& filter_out, uint32_t prealloc = 10UL, uint32_t chunk_size = 1) : FrameAcquisitionPolicy(filter_out) {
|
|
195
|
+
m_writer = std::make_unique<XcpLogFileWriter>(file_name, prealloc, chunk_size);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
FrameRecorderPolicy() = delete;
|
|
199
|
+
FrameRecorderPolicy(const FrameRecorderPolicy&) = delete;
|
|
200
|
+
FrameRecorderPolicy(FrameRecorderPolicy&&) = delete;
|
|
201
|
+
|
|
202
|
+
void feed(FrameCategory frame_category, std::uint32_t counter, std::uint64_t timestamp, const payload_t& payload) override {
|
|
203
|
+
if (m_filter_out && (!(*m_filter_out).contains(frame_category))) {
|
|
204
|
+
m_writer->add_frame(static_cast<std::uint8_t>(frame_category), counter, timestamp, payload.size(), std::bit_cast<const char *>(payload.data()));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
void finalize() override {
|
|
209
|
+
m_writer->finalize();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private:
|
|
213
|
+
std::unique_ptr<XcpLogFileWriter> m_writer;
|
|
214
|
+
};
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
#include <pybind11/chrono.h>
|
|
4
|
+
#include <pybind11/functional.h>
|
|
5
|
+
#include <pybind11/numpy.h>
|
|
6
|
+
#include <pybind11/pybind11.h>
|
|
7
|
+
#include <pybind11/stl.h>
|
|
8
|
+
|
|
9
|
+
#include <cstdint>
|
|
10
|
+
|
|
11
|
+
#include "transport_ext.hpp"
|
|
12
|
+
#include "framing.hpp"
|
|
13
|
+
#include "sxi_framing.hpp"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
namespace py = pybind11;
|
|
17
|
+
using namespace pybind11::literals;
|
|
18
|
+
|
|
19
|
+
using SxiFrLBCN = SxiReceiver< SxiHeaderFormat::LenByte, SxiChecksumType::None>;
|
|
20
|
+
using SxiFrLBC8 = SxiReceiver< SxiHeaderFormat::LenByte, SxiChecksumType::Sum8>;
|
|
21
|
+
using SxiFrLBC16 = SxiReceiver< SxiHeaderFormat::LenByte, SxiChecksumType::Sum16>;
|
|
22
|
+
|
|
23
|
+
using SxiFrLCBCN = SxiReceiver< SxiHeaderFormat::LenCtrByte, SxiChecksumType::None>;
|
|
24
|
+
using SxiFrLCBC8 = SxiReceiver< SxiHeaderFormat::LenCtrByte, SxiChecksumType::Sum8>;
|
|
25
|
+
using SxiFrLCBC16 = SxiReceiver< SxiHeaderFormat::LenCtrByte, SxiChecksumType::Sum16>;
|
|
26
|
+
|
|
27
|
+
using SxiFrLFBCN = SxiReceiver< SxiHeaderFormat::LenFillByte, SxiChecksumType::None>;
|
|
28
|
+
using SxiFrLFBC8 = SxiReceiver< SxiHeaderFormat::LenFillByte, SxiChecksumType::Sum8>;
|
|
29
|
+
using SxiFrLFBC16 = SxiReceiver< SxiHeaderFormat::LenFillByte, SxiChecksumType::Sum16>;
|
|
30
|
+
|
|
31
|
+
using SxiFrLWCN = SxiReceiver< SxiHeaderFormat::LenWord, SxiChecksumType::None>;
|
|
32
|
+
using SxiFrLWC8 = SxiReceiver< SxiHeaderFormat::LenWord, SxiChecksumType::Sum8>;
|
|
33
|
+
using SxiFrLWC16 = SxiReceiver< SxiHeaderFormat::LenWord, SxiChecksumType::Sum16>;
|
|
34
|
+
|
|
35
|
+
using SxiFrLCWCN = SxiReceiver< SxiHeaderFormat::LenCtrWord, SxiChecksumType::None>;
|
|
36
|
+
using SxiFrLCWC8 = SxiReceiver< SxiHeaderFormat::LenCtrWord, SxiChecksumType::Sum8>;
|
|
37
|
+
using SxiFrLCWC16 = SxiReceiver< SxiHeaderFormat::LenCtrWord, SxiChecksumType::Sum16>;
|
|
38
|
+
|
|
39
|
+
using SxiFrLFWCN = SxiReceiver< SxiHeaderFormat::LenFillWord, SxiChecksumType::None>;
|
|
40
|
+
using SxiFrLFWC8 = SxiReceiver< SxiHeaderFormat::LenFillWord, SxiChecksumType::Sum8>;
|
|
41
|
+
using SxiFrLFWC16 = SxiReceiver< SxiHeaderFormat::LenFillWord, SxiChecksumType::Sum16>;
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class PyFrameAcquisitionPolicy : public FrameAcquisitionPolicy {
|
|
45
|
+
public:
|
|
46
|
+
|
|
47
|
+
using FrameAcquisitionPolicy::FrameAcquisitionPolicy;
|
|
48
|
+
|
|
49
|
+
void feed(
|
|
50
|
+
FrameCategory frame_category, std::uint32_t counter, std::uint64_t timestamp, const payload_t& payload
|
|
51
|
+
) override {
|
|
52
|
+
PYBIND11_OVERRIDE_PURE(void, FrameAcquisitionPolicy, feed, frame_category, counter, timestamp, payload);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
void finalize() override {
|
|
57
|
+
PYBIND11_OVERRIDE_PURE(void, FrameAcquisitionPolicy, finalize);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
PYBIND11_MODULE(transport_ext, m) {
|
|
63
|
+
m.doc() = "pyXCP transport-layer base classes.";
|
|
64
|
+
|
|
65
|
+
py::enum_<FrameCategory>(m, "FrameCategory")
|
|
66
|
+
.value("METADATA", FrameCategory::META)
|
|
67
|
+
.value("CMD", FrameCategory::CMD)
|
|
68
|
+
.value("RESPONSE", FrameCategory::RES)
|
|
69
|
+
.value("ERROR", FrameCategory::ERR)
|
|
70
|
+
.value("EVENT", FrameCategory::EV)
|
|
71
|
+
.value("SERV", FrameCategory::SERV)
|
|
72
|
+
.value("DAQ", FrameCategory::DAQ)
|
|
73
|
+
.value("STIM", FrameCategory::STIM)
|
|
74
|
+
;
|
|
75
|
+
|
|
76
|
+
py::class_<FrameAcquisitionPolicy, PyFrameAcquisitionPolicy>(m, "FrameAcquisitionPolicy", py::dynamic_attr())
|
|
77
|
+
.def(py::init<const std::optional<FrameAcquisitionPolicy::filter_t>&>(), py::arg("filtered_out") = std::nullopt)
|
|
78
|
+
.def("feed", &FrameAcquisitionPolicy::feed)
|
|
79
|
+
.def("finalize", &FrameAcquisitionPolicy::finalize)
|
|
80
|
+
.def_property_readonly("filtered_out", &FrameAcquisitionPolicy::get_filtered_out)
|
|
81
|
+
;
|
|
82
|
+
|
|
83
|
+
py::class_<LegacyFrameAcquisitionPolicy>(m, "LegacyFrameAcquisitionPolicy", py::dynamic_attr())
|
|
84
|
+
.def(py::init<const std::optional<FrameAcquisitionPolicy::filter_t>&>(), py::arg("filtered_out") = std::nullopt)
|
|
85
|
+
.def("feed", &FrameAcquisitionPolicy::feed)
|
|
86
|
+
.def("finalize", &FrameAcquisitionPolicy::finalize)
|
|
87
|
+
.def_property_readonly("reqQueue", &LegacyFrameAcquisitionPolicy::get_req_queue)
|
|
88
|
+
.def_property_readonly("resQueue", &LegacyFrameAcquisitionPolicy::get_res_queue)
|
|
89
|
+
.def_property_readonly("daqQueue", &LegacyFrameAcquisitionPolicy::get_daq_queue)
|
|
90
|
+
.def_property_readonly("evQueue", &LegacyFrameAcquisitionPolicy::get_ev_queue)
|
|
91
|
+
.def_property_readonly("servQueue", &LegacyFrameAcquisitionPolicy::get_serv_queue)
|
|
92
|
+
.def_property_readonly("metaQueue", &LegacyFrameAcquisitionPolicy::get_meta_queue)
|
|
93
|
+
.def_property_readonly("errorQueue", &LegacyFrameAcquisitionPolicy::get_error_queue)
|
|
94
|
+
.def_property_readonly("stimQueue", &LegacyFrameAcquisitionPolicy::get_stim_queue)
|
|
95
|
+
;
|
|
96
|
+
|
|
97
|
+
py::class_<NoOpPolicy>(m, "NoOpPolicy", py::dynamic_attr())
|
|
98
|
+
.def(py::init<const std::optional<FrameAcquisitionPolicy::filter_t>&>(), py::arg("filtered_out") = std::nullopt)
|
|
99
|
+
.def("feed", &FrameAcquisitionPolicy::feed)
|
|
100
|
+
.def("finalize", &FrameAcquisitionPolicy::finalize)
|
|
101
|
+
;
|
|
102
|
+
|
|
103
|
+
py::class_<StdoutPolicy>(m, "StdoutPolicy", py::dynamic_attr())
|
|
104
|
+
.def(py::init<const std::optional<FrameAcquisitionPolicy::filter_t>&>(), py::arg("filtered_out") = std::nullopt)
|
|
105
|
+
.def("feed", &FrameAcquisitionPolicy::feed)
|
|
106
|
+
.def("finalize", &FrameAcquisitionPolicy::finalize)
|
|
107
|
+
;
|
|
108
|
+
|
|
109
|
+
py::class_<FrameRecorderPolicy>(m, "FrameRecorderPolicy", py::dynamic_attr())
|
|
110
|
+
.def(py::init<const std::string&, const std::optional<FrameAcquisitionPolicy::filter_t>&, uint32_t, uint32_t>(),
|
|
111
|
+
py::arg("file_name"), py::arg("filtered_out") = std::nullopt, py::arg("prealloc") = 10UL, py::arg("chunk_size") = 1)
|
|
112
|
+
.def("feed", &FrameAcquisitionPolicy::feed)
|
|
113
|
+
.def("finalize", &FrameAcquisitionPolicy::finalize)
|
|
114
|
+
;
|
|
115
|
+
// Transport layer type enum
|
|
116
|
+
py::enum_<XcpTransportLayerType>(m, "XcpTransportLayerType")
|
|
117
|
+
.value("CAN", XcpTransportLayerType::CAN)
|
|
118
|
+
.value("ETH", XcpTransportLayerType::ETH)
|
|
119
|
+
.value("SXI", XcpTransportLayerType::SXI)
|
|
120
|
+
.value("USB", XcpTransportLayerType::USB);
|
|
121
|
+
|
|
122
|
+
// XCP checksum type enum
|
|
123
|
+
py::enum_<ChecksumType>(m, "ChecksumType")
|
|
124
|
+
.value("NO_CHECKSUM", ChecksumType::NO_CHECKSUM)
|
|
125
|
+
.value("BYTE_CHECKSUM", ChecksumType::BYTE_CHECKSUM)
|
|
126
|
+
.value("WORD_CHECKSUM", ChecksumType::WORD_CHECKSUM);
|
|
127
|
+
|
|
128
|
+
// XCP framing configuration and helper
|
|
129
|
+
py::class_<XcpFramingConfig>(m, "XcpFramingConfig")
|
|
130
|
+
.def(py::init<>())
|
|
131
|
+
.def(py::init<XcpTransportLayerType, std::uint8_t, std::uint8_t, std::uint8_t, bool, ChecksumType>(),
|
|
132
|
+
"transport_layer_type"_a, "header_len"_a, "header_ctr"_a, "header_fill"_a, "tail_fill"_a = false, "tail_cs"_a = ChecksumType::NO_CHECKSUM)
|
|
133
|
+
.def_property_readonly("transport_layer_type", [](const XcpFramingConfig &self) { return self.transport_layer_type; })
|
|
134
|
+
.def_property_readonly("header_len", [](const XcpFramingConfig &self) { return self.header_len; })
|
|
135
|
+
.def_property_readonly("header_ctr", [](const XcpFramingConfig &self) { return self.header_ctr; })
|
|
136
|
+
.def_property_readonly("header_fill", [](const XcpFramingConfig &self) { return self.header_fill; })
|
|
137
|
+
.def_property_readonly("tail_fill", [](const XcpFramingConfig &self) { return self.tail_fill; })
|
|
138
|
+
.def_property_readonly("tail_cs", [](const XcpFramingConfig &self) { return self.tail_cs; });
|
|
139
|
+
|
|
140
|
+
py::class_<XcpFraming>(m, "XcpFraming")
|
|
141
|
+
.def(py::init<const XcpFramingConfig&>())
|
|
142
|
+
.def("prepare_request", [](XcpFraming &self, std::uint32_t cmd, py::bytes data) {
|
|
143
|
+
std::string s = data;
|
|
144
|
+
std::vector<uint8_t> data_vec(s.begin(), s.end());
|
|
145
|
+
return self.prepare_request(cmd, data_vec);
|
|
146
|
+
}, "cmd"_a, "data"_a)
|
|
147
|
+
.def("prepare_request", [](XcpFraming &self, std::uint32_t cmd, py::args data) {
|
|
148
|
+
std::vector<uint8_t> data_vec;
|
|
149
|
+
for (auto item : data) {
|
|
150
|
+
data_vec.push_back(py::cast<uint8_t>(item));
|
|
151
|
+
}
|
|
152
|
+
return self.prepare_request(cmd, data_vec);
|
|
153
|
+
}, "cmd"_a)
|
|
154
|
+
.def("unpack_header", &XcpFraming::unpack_header, py::arg("data"), py::arg("initial_offset") = 0)
|
|
155
|
+
.def("verify_checksum", &XcpFraming::verify_checksum)
|
|
156
|
+
.def_property("counter_send", &XcpFraming::get_counter_send, &XcpFraming::set_counter_send)
|
|
157
|
+
.def_property_readonly("header_size", &XcpFraming::get_header_size);
|
|
158
|
+
|
|
159
|
+
py::class_<SxiFrLBCN>(m, "SxiFrLBCN")
|
|
160
|
+
.def(py::init<std::function<void(const std::vector<uint8_t>&, uint16_t, uint16_t)>>(), py::arg("dispatch_handler"))
|
|
161
|
+
.def("feed_bytes", &SxiFrLBCN::feed_bytes, py::arg("data"))
|
|
162
|
+
;
|
|
163
|
+
|
|
164
|
+
py::class_<SxiFrLBC8>(m, "SxiFrLBC8")
|
|
165
|
+
.def(py::init<std::function<void(const std::vector<uint8_t>&, uint16_t, uint16_t)>>(), py::arg("dispatch_handler"))
|
|
166
|
+
.def("feed_bytes", &SxiFrLBC8::feed_bytes, py::arg("data"))
|
|
167
|
+
;
|
|
168
|
+
|
|
169
|
+
py::class_<SxiFrLBC16>(m, "SxiFrLBC16")
|
|
170
|
+
.def(py::init<std::function<void(const std::vector<uint8_t>&, uint16_t, uint16_t)>>(), py::arg("dispatch_handler"))
|
|
171
|
+
.def("feed_bytes", &SxiFrLBC16::feed_bytes, py::arg("data"))
|
|
172
|
+
;
|
|
173
|
+
|
|
174
|
+
py::class_<SxiFrLCBCN>(m, "SxiFrLCBCN")
|
|
175
|
+
.def(py::init<std::function<void(const std::vector<uint8_t>&, uint16_t, uint16_t)>>(), py::arg("dispatch_handler"))
|
|
176
|
+
.def("feed_bytes", &SxiFrLCBCN::feed_bytes, py::arg("data"))
|
|
177
|
+
;
|
|
178
|
+
|
|
179
|
+
py::class_<SxiFrLCBC8>(m, "SxiFrLCBC8")
|
|
180
|
+
.def(py::init<std::function<void(const std::vector<uint8_t>&, uint16_t, uint16_t)>>(), py::arg("dispatch_handler"))
|
|
181
|
+
.def("feed_bytes", &SxiFrLCBC8::feed_bytes, py::arg("data"))
|
|
182
|
+
;
|
|
183
|
+
|
|
184
|
+
py::class_<SxiFrLCBC16>(m, "SxiFrLCBC16")
|
|
185
|
+
.def(py::init<std::function<void(const std::vector<uint8_t>&, uint16_t, uint16_t)>>(), py::arg("dispatch_handler"))
|
|
186
|
+
.def("feed_bytes", &SxiFrLCBC16::feed_bytes, py::arg("data"))
|
|
187
|
+
;
|
|
188
|
+
|
|
189
|
+
py::class_<SxiFrLFBCN>(m, "SxiFrLFBCN")
|
|
190
|
+
.def(py::init<std::function<void(const std::vector<uint8_t>&, uint16_t, uint16_t)>>(), py::arg("dispatch_handler"))
|
|
191
|
+
.def("feed_bytes", &SxiFrLFBCN::feed_bytes, py::arg("data"))
|
|
192
|
+
;
|
|
193
|
+
|
|
194
|
+
py::class_<SxiFrLFBC8>(m, "SxiFrLFBC8")
|
|
195
|
+
.def(py::init<std::function<void(const std::vector<uint8_t>&, uint16_t, uint16_t)>>(), py::arg("dispatch_handler"))
|
|
196
|
+
.def("feed_bytes", &SxiFrLFBC8::feed_bytes, py::arg("data"))
|
|
197
|
+
;
|
|
198
|
+
|
|
199
|
+
py::class_<SxiFrLFBC16>(m, "SxiFrLFBC16")
|
|
200
|
+
.def(py::init<std::function<void(const std::vector<uint8_t>&, uint16_t, uint16_t)>>(), py::arg("dispatch_handler"))
|
|
201
|
+
.def("feed_bytes", &SxiFrLFBC16::feed_bytes, py::arg("data"))
|
|
202
|
+
;
|
|
203
|
+
|
|
204
|
+
py::class_<SxiFrLWCN>(m, "SxiFrLWCN")
|
|
205
|
+
.def(py::init<std::function<void(const std::vector<uint8_t>&, uint16_t, uint16_t)>>(), py::arg("dispatch_handler"))
|
|
206
|
+
.def("feed_bytes", &SxiFrLWCN::feed_bytes, py::arg("data"))
|
|
207
|
+
;
|
|
208
|
+
|
|
209
|
+
py::class_<SxiFrLWC8>(m, "SxiFrLWC8")
|
|
210
|
+
.def(py::init<std::function<void(const std::vector<uint8_t>&, uint16_t, uint16_t)>>(), py::arg("dispatch_handler"))
|
|
211
|
+
.def("feed_bytes", &SxiFrLWC8::feed_bytes, py::arg("data"))
|
|
212
|
+
;
|
|
213
|
+
|
|
214
|
+
py::class_<SxiFrLWC16>(m, "SxiFrLWC16")
|
|
215
|
+
.def(py::init<std::function<void(const std::vector<uint8_t>&, uint16_t, uint16_t)>>(), py::arg("dispatch_handler"))
|
|
216
|
+
.def("feed_bytes", &SxiFrLWC16::feed_bytes, py::arg("data"))
|
|
217
|
+
;
|
|
218
|
+
|
|
219
|
+
py::class_<SxiFrLCWCN>(m, "SxiFrLCWCN")
|
|
220
|
+
.def(py::init<std::function<void(const std::vector<uint8_t>&, uint16_t, uint16_t)>>(), py::arg("dispatch_handler"))
|
|
221
|
+
.def("feed_bytes", &SxiFrLCWCN::feed_bytes, py::arg("data"))
|
|
222
|
+
;
|
|
223
|
+
|
|
224
|
+
py::class_<SxiFrLCWC8>(m, "SxiFrLCWC8")
|
|
225
|
+
.def(py::init<std::function<void(const std::vector<uint8_t>&, uint16_t, uint16_t)>>(), py::arg("dispatch_handler"))
|
|
226
|
+
.def("feed_bytes", &SxiFrLCWC8::feed_bytes, py::arg("data"))
|
|
227
|
+
;
|
|
228
|
+
|
|
229
|
+
py::class_<SxiFrLCWC16>(m, "SxiFrLCWC16")
|
|
230
|
+
.def(py::init<std::function<void(const std::vector<uint8_t>&, uint16_t, uint16_t)>>(), py::arg("dispatch_handler"))
|
|
231
|
+
.def("feed_bytes", &SxiFrLCWC16::feed_bytes, py::arg("data"))
|
|
232
|
+
;
|
|
233
|
+
|
|
234
|
+
py::class_<SxiFrLFWCN>(m, "SxiFrLFWCN")
|
|
235
|
+
.def(py::init<std::function<void(const std::vector<uint8_t>&, uint16_t, uint16_t)>>(), py::arg("dispatch_handler"))
|
|
236
|
+
.def("feed_bytes", &SxiFrLFWCN::feed_bytes, py::arg("data"))
|
|
237
|
+
;
|
|
238
|
+
|
|
239
|
+
py::class_<SxiFrLFWC8>(m, "SxiFrLFWC8")
|
|
240
|
+
.def(py::init<std::function<void(const std::vector<uint8_t>&, uint16_t, uint16_t)>>(), py::arg("dispatch_handler"))
|
|
241
|
+
.def("feed_bytes", &SxiFrLFWC8::feed_bytes, py::arg("data"))
|
|
242
|
+
;
|
|
243
|
+
|
|
244
|
+
py::class_<SxiFrLFWC16>(m, "SxiFrLFWC16")
|
|
245
|
+
.def(py::init<std::function<void(const std::vector<uint8_t>&, uint16_t, uint16_t)>>(), py::arg("dispatch_handler"))
|
|
246
|
+
.def("feed_bytes", &SxiFrLFWC16::feed_bytes, py::arg("data"))
|
|
247
|
+
;
|
|
248
|
+
|
|
249
|
+
}
|
pyxcp/transport/usb_transport.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import threading
|
|
4
4
|
from array import array
|
|
5
5
|
from collections import deque
|
|
@@ -12,7 +12,13 @@ import usb.core
|
|
|
12
12
|
import usb.util
|
|
13
13
|
from usb.core import USBError, USBTimeoutError
|
|
14
14
|
|
|
15
|
-
from pyxcp.transport.base import
|
|
15
|
+
from pyxcp.transport.base import (
|
|
16
|
+
BaseTransport,
|
|
17
|
+
ChecksumType,
|
|
18
|
+
XcpFramingConfig,
|
|
19
|
+
XcpTransportLayerType,
|
|
20
|
+
parse_header_format,
|
|
21
|
+
)
|
|
16
22
|
from pyxcp.utils import short_sleep
|
|
17
23
|
|
|
18
24
|
|
|
@@ -23,21 +29,26 @@ FIVE_MS = 5_000_000 # Five milliseconds in nanoseconds.
|
|
|
23
29
|
class Usb(BaseTransport):
|
|
24
30
|
""""""
|
|
25
31
|
|
|
26
|
-
HEADER = struct.Struct("<2H")
|
|
27
|
-
HEADER_SIZE = HEADER.size
|
|
28
|
-
|
|
29
32
|
def __init__(self, config=None, policy=None, transport_layer_interface: Optional[usb.core.Device] = None):
|
|
30
|
-
super().__init__(config, policy, transport_layer_interface)
|
|
31
33
|
self.load_config(config)
|
|
34
|
+
header_len, header_ctr, header_fill = parse_header_format(self.config.header_format)
|
|
35
|
+
framing_config = XcpFramingConfig(
|
|
36
|
+
transport_layer_type=XcpTransportLayerType.USB,
|
|
37
|
+
header_len=header_len,
|
|
38
|
+
header_ctr=header_ctr,
|
|
39
|
+
header_fill=header_fill,
|
|
40
|
+
tail_fill=False,
|
|
41
|
+
tail_cs=ChecksumType.NO_CHECKSUM,
|
|
42
|
+
)
|
|
32
43
|
self.serial_number: str = self.config.serial_number
|
|
33
44
|
self.vendor_id: int = self.config.vendor_id
|
|
34
45
|
self.product_id: int = self.config.product_id
|
|
35
46
|
self.configuration_number: int = self.config.configuration_number
|
|
36
47
|
self.interface_number: int = self.config.interface_number
|
|
37
48
|
self.library: str = self.config.library
|
|
38
|
-
self.header_format: str = self.config.header_format
|
|
39
49
|
self.library = self.config.get("library")
|
|
40
50
|
self.device = None
|
|
51
|
+
super().__init__(config, framing_config, policy, transport_layer_interface)
|
|
41
52
|
|
|
42
53
|
## IN-EP (RES/ERR, DAQ, and EV/SERV) Parameters.
|
|
43
54
|
self.in_ep_number: int = self.config.in_ep_number
|
|
@@ -58,6 +69,7 @@ class Usb(BaseTransport):
|
|
|
58
69
|
target=self._packet_listen,
|
|
59
70
|
args=(),
|
|
60
71
|
kwargs={},
|
|
72
|
+
daemon=True,
|
|
61
73
|
)
|
|
62
74
|
self._packets = deque()
|
|
63
75
|
|
|
@@ -109,17 +121,23 @@ class Usb(BaseTransport):
|
|
|
109
121
|
def start_listener(self):
|
|
110
122
|
super().start_listener()
|
|
111
123
|
if self._packet_listener.is_alive():
|
|
112
|
-
self._packet_listener.join()
|
|
113
|
-
self._packet_listener = threading.Thread(target=self._packet_listen)
|
|
124
|
+
self._packet_listener.join(timeout=2.0)
|
|
125
|
+
self._packet_listener = threading.Thread(target=self._packet_listen, daemon=True)
|
|
114
126
|
self._packet_listener.start()
|
|
115
127
|
|
|
116
128
|
def close(self):
|
|
117
129
|
"""Close the transport-layer connection and event-loop."""
|
|
118
130
|
self.finish_listener()
|
|
119
|
-
|
|
120
|
-
self.listener.
|
|
121
|
-
|
|
122
|
-
|
|
131
|
+
try:
|
|
132
|
+
if self.listener.is_alive():
|
|
133
|
+
self.listener.join(timeout=2.0)
|
|
134
|
+
except Exception:
|
|
135
|
+
pass
|
|
136
|
+
try:
|
|
137
|
+
if self._packet_listener.is_alive():
|
|
138
|
+
self._packet_listener.join(timeout=2.0)
|
|
139
|
+
except Exception:
|
|
140
|
+
pass
|
|
123
141
|
self.close_connection()
|
|
124
142
|
|
|
125
143
|
def _packet_listen(self):
|
|
@@ -148,9 +166,19 @@ class Usb(BaseTransport):
|
|
|
148
166
|
self.status = 0 # disconnected
|
|
149
167
|
break
|
|
150
168
|
|
|
169
|
+
def send(self, frame):
|
|
170
|
+
self.pre_send_timestamp = self.timestamp.value
|
|
171
|
+
try:
|
|
172
|
+
self.out_ep.write(frame)
|
|
173
|
+
except (USBError, USBTimeoutError):
|
|
174
|
+
# sometimes usb.core.USBError: [Errno 5] Input/Output Error is raised
|
|
175
|
+
# even though the command is send and a reply is received from the device.
|
|
176
|
+
# Ignore this here since a Timeout error will be raised anyway if
|
|
177
|
+
# the device does not respond
|
|
178
|
+
pass
|
|
179
|
+
self.post_send_timestamp = self.timestamp.value
|
|
180
|
+
|
|
151
181
|
def listen(self):
|
|
152
|
-
HEADER_UNPACK_FROM = self.HEADER.unpack_from
|
|
153
|
-
HEADER_SIZE = self.HEADER_SIZE
|
|
154
182
|
popleft = self._packets.popleft
|
|
155
183
|
process_response = self.process_response
|
|
156
184
|
close_event_set = self.closeEvent.is_set
|
|
@@ -178,10 +206,10 @@ class Usb(BaseTransport):
|
|
|
178
206
|
short_sleep()
|
|
179
207
|
last_sleep = self.timestamp.value
|
|
180
208
|
if length is None:
|
|
181
|
-
if current_size >=
|
|
182
|
-
length, counter =
|
|
183
|
-
current_position +=
|
|
184
|
-
current_size -=
|
|
209
|
+
if current_size >= self.framing.header_size:
|
|
210
|
+
length, counter = self.framing.unpack_header(bytes(data), initial_offset=current_position)
|
|
211
|
+
current_position += self.framing.header_size
|
|
212
|
+
current_size -= self.framing.header_size
|
|
185
213
|
else:
|
|
186
214
|
data = data[current_position:]
|
|
187
215
|
break
|
|
@@ -196,18 +224,6 @@ class Usb(BaseTransport):
|
|
|
196
224
|
data = data[current_position:]
|
|
197
225
|
break
|
|
198
226
|
|
|
199
|
-
def send(self, frame):
|
|
200
|
-
self.pre_send_timestamp = self.timestamp.value
|
|
201
|
-
try:
|
|
202
|
-
self.out_ep.write(frame)
|
|
203
|
-
except (USBError, USBTimeoutError):
|
|
204
|
-
# sometimes usb.core.USBError: [Errno 5] Input/Output Error is raised
|
|
205
|
-
# even though the command is send and a reply is received from the device.
|
|
206
|
-
# Ignore this here since a Timeout error will be raised anyway if
|
|
207
|
-
# the device does not respond
|
|
208
|
-
pass
|
|
209
|
-
self.post_send_timestamp = self.timestamp.value
|
|
210
|
-
|
|
211
227
|
def close_connection(self):
|
|
212
228
|
if self.device is not None:
|
|
213
229
|
usb.util.dispose_resources(self.device)
|
pyxcp/types.py
CHANGED
|
@@ -978,19 +978,6 @@ class XcpGetSeedMode(enum.IntEnum):
|
|
|
978
978
|
REMAINING = 1
|
|
979
979
|
|
|
980
980
|
|
|
981
|
-
class FrameCategory(enum.IntEnum):
|
|
982
|
-
"""XCP frame categories."""
|
|
983
|
-
|
|
984
|
-
METADATA = 0
|
|
985
|
-
CMD = 1
|
|
986
|
-
RESPONSE = 2
|
|
987
|
-
ERROR = 3
|
|
988
|
-
EVENT = 4
|
|
989
|
-
SERV = 5
|
|
990
|
-
DAQ = 6
|
|
991
|
-
STIM = 7
|
|
992
|
-
|
|
993
|
-
|
|
994
981
|
class TryCommandResult(enum.IntEnum):
|
|
995
982
|
""" """
|
|
996
983
|
|
|
@@ -6,7 +6,7 @@ import sys
|
|
|
6
6
|
from binascii import hexlify
|
|
7
7
|
from enum import IntEnum
|
|
8
8
|
from time import perf_counter, sleep
|
|
9
|
-
from typing import
|
|
9
|
+
from typing import Union
|
|
10
10
|
|
|
11
11
|
import chardet
|
|
12
12
|
import pytz
|
|
@@ -86,7 +86,6 @@ def delay(amount: float):
|
|
|
86
86
|
|
|
87
87
|
|
|
88
88
|
class CurrentDatetime(TimestampInfo):
|
|
89
|
-
|
|
90
89
|
def __init__(self, timestamp_ns: int):
|
|
91
90
|
TimestampInfo.__init__(self, timestamp_ns)
|
|
92
91
|
timezone = pytz.timezone(self.timezone)
|