Coverage for d7a/alp/command.py: 74%
164 statements
« prev ^ index » next coverage.py v7.5.0, created at 2024-05-24 08:03 +0200
« prev ^ index » next coverage.py v7.5.0, created at 2024-05-24 08:03 +0200
1#
2# Copyright (c) 2015-2021 University of Antwerp, Aloxy NV.
3#
4# This file is part of pyd7a.
5# See https://github.com/Sub-IoT/pyd7a for further info.
6#
7# Licensed under the Apache License, Version 2.0 (the "License");
8# you may not use this file except in compliance with the License.
9# You may obtain a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS,
15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16# See the License for the specific language governing permissions and
17# limitations under the License.
18#
20# author: Christophe VG <contact@christophe.vg>
21# class implementation of ALP commands
23# a D7A ALP Command consists of 1 or more ALP Actions
24import random
26from d7a.alp.action import Action
27from d7a.alp.interface import InterfaceType
28from d7a.alp.operands.file import DataRequest, Data, FileIdOperand
29from d7a.alp.operands.file_header import FileHeaderOperand
30from d7a.alp.operands.length import Length
31from d7a.alp.operands.offset import Offset
32from d7a.alp.operands.interface_configuration import InterfaceConfiguration
33from d7a.alp.operands.tag_id import TagId
34from d7a.alp.operations.file_management import CreateNewFile
35from d7a.alp.operations.forward import Forward
36from d7a.alp.operations.indirect_forward import IndirectForward
37from d7a.alp.operands.indirect_interface_operand import IndirectInterfaceOperand
38from d7a.alp.indirect_forward_action import IndirectForwardAction
39from d7a.alp.operations.requests import ReadFileData, ReadFileHeader
40from d7a.alp.operations.responses import ReturnFileData
41from d7a.alp.operations.tag_request import TagRequest
42from d7a.alp.operations.write_operations import WriteFileData, WriteFileHeader
43from d7a.alp.status_action import StatusAction, StatusActionOperandExtensions
44from d7a.alp.tag_response_action import TagResponseAction
45from d7a.parse_error import ParseError
46from d7a.sp.configuration import Configuration
48from d7a.support.schema import Validatable, Types
49from d7a.alp.regular_action import RegularAction
50from d7a.alp.tag_request_action import TagRequestAction
53class Command(Validatable):
55 SCHEMA = [{
56 "actions": Types.LIST(Action),
57 "interface_status": Types.OBJECT(StatusAction, nullable=True) # can be null for example when parsing DLL frames
58 }]
60 def __init__(self, actions=[], generate_tag_request_action=True, tag_id=None, send_tag_response_when_completed=True):
61 self.actions = []
62 self.interface_status = None
63 self.generate_tag_request_action = generate_tag_request_action
64 self.tag_id = tag_id
65 self.send_tag_response_when_completed = send_tag_response_when_completed
66 self.execution_completed = False
68 for action in actions:
69 if type(action) == StatusAction and action.status_operand_extension == StatusActionOperandExtensions.INTERFACE_STATUS:
70 self.interface_status = action
71 elif type(action) == TagRequestAction:
72 if (self.tag_id != None) and (self.tag_id != action.operand.tag_id):
73 raise ParseError("tag request action has different tag id than previous action")
74 self.tag_id = action.operand.tag_id
75 self.send_tag_response_when_completed = action.respond_when_completed
76 # we don't add this to self.actions but prepend it on serializing
77 elif type(action) == TagResponseAction:
78 if (self.tag_id != None) and (self.tag_id != action.operand.tag_id):
79 raise ParseError("tag response action has different tag id than previous action")
80 self.tag_id = action.operand.tag_id
81 self.completed_with_error = action.error # TODO distinguish between commands and responses?
82 self.execution_completed = action.eop
83 else:
84 self.actions.append(action)
86 if self.generate_tag_request_action and self.tag_id == None:
87 self.tag_id = random.randint(0, 255)
89 super(Command, self).__init__()
91 def add_action(self, action):
92 self.actions.append(action)
94 def add_forward_action(self, interface_type=InterfaceType.HOST, interface_configuration=None):
95 if interface_configuration is not None and interface_type == InterfaceType.HOST:
96 raise ValueError("interface_configuration is not supported for interface_type HOST")
98 if interface_type == InterfaceType.D7ASP:
99 if interface_configuration is None:
100 interface_configuration = Configuration()
102 self.actions.append(
103 RegularAction(
104 operation=Forward(
105 operand=InterfaceConfiguration(
106 interface_id=InterfaceType.D7ASP,
107 interface_configuration=interface_configuration
108 )
109 )
110 )
111 )
112 elif interface_type == InterfaceType.SERIAL:
113 self.actions.append(
114 RegularAction(
115 operation=Forward(
116 operand=InterfaceConfiguration(
117 interface_id=InterfaceType.SERIAL
118 )
119 )
120 )
121 )
122 elif interface_type == InterfaceType.LORAWAN_ABP:
123 self.actions.append(
124 RegularAction(
125 operation=Forward(
126 operand=InterfaceConfiguration(
127 interface_id=InterfaceType.LORAWAN_ABP,
128 interface_configuration=interface_configuration
129 )
130 )
131 )
132 )
133 elif interface_type == InterfaceType.LORAWAN_OTAA:
134 self.actions.append(
135 RegularAction(
136 operation=Forward(
137 operand=InterfaceConfiguration(
138 interface_id=InterfaceType.LORAWAN_OTAA,
139 interface_configuration=interface_configuration
140 )
141 )
142 )
143 )
144 elif interface_type == InterfaceType.HOST:
145 pass
146 else:
147 raise ValueError("interface_type {} is not supported".format(interface_type))
149 def prepend_forward_action(self, interface_type=InterfaceType.HOST, interface_configuration=None):
150 self.actions.insert(0,
151 RegularAction(
152 operation=Forward(
153 operand=InterfaceConfiguration(
154 interface_id=interface_type,
155 interface_configuration=interface_configuration
156 )
157 )
158 )
159 )
161 def add_tag_request_action(self, tag_id=None, send_tag_when_completed=None):
162 tag_id = self.tag_id if tag_id is None else tag_id
163 send_tag_when_completed = self.send_tag_response_when_completed if send_tag_when_completed is None else send_tag_when_completed
164 self.actions.append(
165 TagRequestAction(
166 respond_when_completed=send_tag_when_completed,
167 operation=TagRequest(
168 operand=TagId(tag_id=tag_id)
169 )
170 )
171 )
173 def add_indirect_forward_action(self, interface_file_id=None, overload=False, overload_configuration=None):
174 if not overload and (overload_configuration is not None):
175 overload_configuration = None
177 self.actions.append(
178 IndirectForwardAction(
179 overload=overload,
180 operation=IndirectForward(
181 operand=IndirectInterfaceOperand(
182 interface_file_id=interface_file_id,
183 interface_configuration_overload=overload_configuration
184 )
185 )
186 )
187 )
189 @staticmethod
190 def create_with_read_file_action_system_file(file, interface_type=InterfaceType.HOST, interface_configuration=None):
191 # default to host interface, when D7ASP interface is used prepend with Forward action
192 cmd = Command()
193 cmd.add_forward_action(interface_type, interface_configuration)
194 cmd.add_action(
195 RegularAction(
196 operation=ReadFileData(
197 operand=DataRequest(
198 offset=Offset(id=file.id, offset=Length(0)), # TODO offset size
199 length=Length(file.length)
200 )
201 )
202 )
203 )
205 return cmd
207 @staticmethod
208 def create_with_read_file_action(file_id, length, offset=0, interface_type=InterfaceType.HOST, interface_configuration=None):
209 # default to host interface, when D7ASP interface is used prepend with Forward action
210 cmd = Command()
211 cmd.add_forward_action(interface_type, interface_configuration)
212 cmd.add_action(
213 RegularAction(
214 operation=ReadFileData(
215 operand=DataRequest(
216 offset=Offset(id=file_id, offset=Length(offset)), # TODO offset size
217 length=Length(length)
218 )
219 )
220 )
221 )
223 return cmd
225 @staticmethod
226 def create_with_write_file_action(file_id, data, offset=0, interface_type=InterfaceType.HOST, interface_configuration=None):
227 # default to host interface, when D7ASP interface is used prepend with Forward action
228 cmd = Command()
229 cmd.add_forward_action(interface_type, interface_configuration)
230 cmd.add_action(
231 RegularAction(
232 operation=WriteFileData(
233 operand=Data(
234 offset=Offset(id=file_id, offset=Length(offset)), # TODO offset size
235 data=data
236 )
237 )
238 )
239 )
241 return cmd
243 @staticmethod
244 def create_with_write_file_action_system_file(file, interface_type=InterfaceType.HOST, interface_configuration=None):
245 # default to host interface, when D7ASP interface is used prepend with Forward action
246 cmd = Command()
247 cmd.add_forward_action(interface_type, interface_configuration)
248 cmd.add_action(
249 RegularAction(
250 operation=WriteFileData(
251 operand=Data(
252 offset=Offset(id=file.id),
253 data=list(file)
254 )
255 )
256 )
257 )
259 return cmd
261 @staticmethod
262 def create_with_return_file_data_action(file_id, data, offset=0, tag_id=None, interface_type=InterfaceType.HOST, interface_configuration=None):
263 # default to host interface, when D7ASP interface is used prepend with Forward action
264 cmd = Command(tag_id=tag_id)
265 cmd.add_forward_action(interface_type, interface_configuration)
266 cmd.add_action(
267 RegularAction(
268 operation=ReturnFileData(
269 operand=Data(
270 data=data,
271 offset=Offset(id=file_id, offset=Length(offset))
272 )
273 )
274 )
275 )
277 return cmd
279 @staticmethod
280 def create_with_read_file_header(file_id, interface_type=InterfaceType.HOST, interface_configuration=None):
281 cmd = Command()
282 cmd.add_forward_action(interface_type, interface_configuration)
283 cmd.add_action(
284 RegularAction(
285 operation=ReadFileHeader(
286 operand=FileIdOperand(
287 file_id=file_id
288 )
289 )
290 )
291 )
293 return cmd
296 @staticmethod
297 def create_with_write_file_header(file_id, file_header, interface_type=InterfaceType.HOST, interface_configuration=None):
298 cmd = Command()
299 cmd.add_forward_action(interface_type, interface_configuration)
300 cmd.add_action(
301 RegularAction(
302 operation=WriteFileHeader(
303 operand=FileHeaderOperand(
304 file_id=file_id,
305 file_header=file_header
306 )
307 )
308 )
309 )
311 return cmd
313 @staticmethod
314 def create_with_create_new_file(file_id, file_header, interface_type=InterfaceType.HOST, interface_configuration=None):
315 cmd = Command()
316 cmd.add_forward_action(interface_type, interface_configuration)
317 cmd.add_action(
318 RegularAction(
319 operation=CreateNewFile(
320 operand=FileHeaderOperand(
321 file_id=file_id,
322 file_header=file_header
323 )
324 )
325 )
326 )
328 return cmd
330 def __iter__(self):
331 if self.generate_tag_request_action:
332 tag_request_action = TagRequestAction(
333 respond_when_completed=self.send_tag_response_when_completed,
334 operation=TagRequest(
335 operand=TagId(tag_id=self.tag_id)
336 )
337 )
338 for byte in tag_request_action:
339 yield byte
341 if self.interface_status is not None:
342 for byte in self.interface_status:
343 yield byte
345 for action in self.actions:
346 for byte in action:
347 yield byte
349 def describe_actions(self):
350 description = ""
351 for action in self.actions:
352 description = description + "{}, ".format(action)
354 return description.strip(", ")
356 def get_d7asp_interface_status(self):
357 if self.interface_status is None or self.interface_status.operand.interface_id != 0xD7:
358 return None
360 return self.interface_status.operation.operand.interface_status
362 def __str__(self):
363 output = "Command with tag {} ".format(self.tag_id)
364 if(self.execution_completed):
365 status = "completed"
366 if(self.completed_with_error):
367 status += ", with error"
368 else:
369 status += ", without error"
370 else:
371 status = "executing"
373 output += "({})".format(status)
375 if(len(self.actions) > 0):
376 output += "\n\tactions:\n"
377 for action in self.actions:
378 output += "\t\taction: {}\n".format(action)
380 if self.interface_status is not None:
381 output += "\tinterface status: {}\n".format(self.interface_status)
382 return output