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

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# 

19 

20# author: Christophe VG <contact@christophe.vg> 

21# class implementation of ALP commands 

22 

23# a D7A ALP Command consists of 1 or more ALP Actions 

24import random 

25 

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 

47 

48from d7a.support.schema import Validatable, Types 

49from d7a.alp.regular_action import RegularAction 

50from d7a.alp.tag_request_action import TagRequestAction 

51 

52 

53class Command(Validatable): 

54 

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 }] 

59 

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 

67 

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) 

85 

86 if self.generate_tag_request_action and self.tag_id == None: 

87 self.tag_id = random.randint(0, 255) 

88 

89 super(Command, self).__init__() 

90 

91 def add_action(self, action): 

92 self.actions.append(action) 

93 

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") 

97 

98 if interface_type == InterfaceType.D7ASP: 

99 if interface_configuration is None: 

100 interface_configuration = Configuration() 

101 

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)) 

148 

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 ) 

160 

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 ) 

172 

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 

176 

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 ) 

188 

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 ) 

204 

205 return cmd 

206 

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 ) 

222 

223 return cmd 

224 

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 ) 

240 

241 return cmd 

242 

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 ) 

258 

259 return cmd 

260 

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 ) 

276 

277 return cmd 

278 

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 ) 

292 

293 return cmd 

294 

295 

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 ) 

310 

311 return cmd 

312 

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 ) 

327 

328 return cmd 

329 

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 

340 

341 if self.interface_status is not None: 

342 for byte in self.interface_status: 

343 yield byte 

344 

345 for action in self.actions: 

346 for byte in action: 

347 yield byte 

348 

349 def describe_actions(self): 

350 description = "" 

351 for action in self.actions: 

352 description = description + "{}, ".format(action) 

353 

354 return description.strip(", ") 

355 

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 

359 

360 return self.interface_status.operation.operand.interface_status 

361 

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" 

372 

373 output += "({})".format(status) 

374 

375 if(len(self.actions) > 0): 

376 output += "\n\tactions:\n" 

377 for action in self.actions: 

378 output += "\t\taction: {}\n".format(action) 

379 

380 if self.interface_status is not None: 

381 output += "\tinterface status: {}\n".format(self.interface_status) 

382 return output