Projects STRLCPY CatSniffer Commits dc54f172
🤬
  • ■ ■ ■ ■ ■ ■
    firmware/LoRaCatSniffer/LoRaCatSniffer.ino
     1 +/*
     2 + CatSniffer - Use LoRa for communication with the SX1262 module
     3 +
     4 + Andres Sabas @ Electronic Cats
     5 + Original Creation Date: Jal 23, 2021
     6 + This code is beerware; if you see me (or any other Electronic Cats
     7 + member) at the local, and you've found our code helpful,
     8 + please buy us a round!
     9 + Distributed as-is; no warranty is given.
     10 +*/
     11 + 
     12 +#define SERIALCOMMAND_HARDWAREONLY
     13 +
     14 +#include <SerialCommand.h>
     15 +#include <RadioLib.h>
     16 + 
     17 +#define CTF1 14
     18 +#define CTF2 11
     19 +#define CTF3 10
     20 + 
     21 +SerialCommand SCmd;
     22 + 
     23 +float fwVersion= 0.1;
     24 + 
     25 +float frequency = 915;
     26 +int spreadFactor = 8;
     27 +int bwReference = 7;
     28 +int codingRate = 5;
     29 +byte syncWord = 0x12;
     30 +int preambleLength = 8;
     31 +int outputPower = 20;
     32 +int channel = 0;
     33 +bool rx_status = false;
     34 + 
     35 +// SX1262 has the following connections:
     36 +// NSS pin: 17
     37 +// DIO1 pin: 3
     38 +// NRST pin: 8
     39 +// BUSY pin: 2
     40 +SX1262 radio = new Module(17, 3, 8, 2);
     41 + 
     42 +// or using RadioShield
     43 +// https://github.com/jgromes/RadioShield
     44 +//SX1262 radio = RadioShield.ModuleA;
     45 + 
     46 +void setup() {
     47 + Serial.begin(9600);
     48 + while(!Serial);
     49 + 
     50 + Serial.println("Welcome to the LoRa Sniffer CLI " + String(fwVersion,1) + "v\n");
     51 + Serial.println("With this sketch you can scan the LoRa spectrum");
     52 + Serial.println("Changing the Frequency, Spreading Factor, BandWidth or the IQ signals of the radio.");
     53 + Serial.println("Type help to get the available commands.");
     54 + Serial.println("Electronic Cats ® 2020");
     55 + 
     56 + // Setup callbacks for SerialCommand commands
     57 + SCmd.addCommand("help",help);
     58 + SCmd.addCommand("set_rx",set_rx);
     59 + SCmd.addCommand("set_tx",set_tx0);
     60 + SCmd.addCommand("set_tx_hex",set_tx1);
     61 + SCmd.addCommand("set_tx_ascii",set_tx2);
     62 + SCmd.addCommand("set_freq",set_freq);
     63 + SCmd.addCommand("set_sf",set_sf);
     64 + SCmd.addCommand("set_bw",set_bw);
     65 + SCmd.addCommand("set_cr",set_cr);
     66 + SCmd.addCommand("set_chann",set_chann);
     67 + SCmd.addCommand("set_sw",set_sw);
     68 + SCmd.addCommand("set_op",set_op);
     69 + SCmd.addCommand("set_pl",set_pl);
     70 + 
     71 + SCmd.addCommand("set_rx",set_rx);
     72 + 
     73 + SCmd.addCommand("get_config",get_config);
     74 + SCmd.addCommand("get_freq",get_freq);
     75 + SCmd.addCommand("get_sf",get_sf);
     76 + SCmd.addCommand("get_bw",get_bw);
     77 + SCmd.addCommand("get_cr",get_cr);
     78 + SCmd.addCommand("get_op",get_op);
     79 + SCmd.addCommand("get_sw",get_sw);
     80 + SCmd.addCommand("get_pl",get_pl);
     81 +
     82 + SCmd.setDefaultHandler(unrecognized); // Handler for command that isn't matched (says "What?")
     83 + 
     84 + 
     85 + pinMode(CTF1, OUTPUT);
     86 + pinMode(CTF2, OUTPUT);
     87 + pinMode(CTF3, OUTPUT);
     88 + 
     89 + digitalWrite(CTF1, HIGH);
     90 + digitalWrite(CTF2, LOW);
     91 + digitalWrite(CTF3, LOW);
     92 + 
     93 +
     94 + // initialize SX1262 with default settings
     95 + Serial.print(F("[SX1262] Initializing ... "));
     96 + //debing(float freq, float bw, uint8_t sf, uint8_t cr, uint8_t syncWord, int8_t power, uint16_t preambleLength, float tcxoVoltage, bool useRegulatorLDO)
     97 + 
     98 + int state = radio.begin(915.0, 250, 7, 5, 0x12, 20, 8, 0, false);
     99 + 
     100 + if (state == ERR_NONE) {
     101 + Serial.println(F("success!"));
     102 + } else {
     103 + Serial.print(F("failed, code "));
     104 + Serial.println(state);
     105 + while (true);
     106 + }
     107 + 
     108 + // set the function that will be called
     109 + // when new packet is received
     110 + radio.setDio1Action(setFlag);
     111 + 
     112 + rx_status = false;
     113 +
     114 + // some modules have an external RF switch
     115 + // controlled via two pins (RX enable, TX enable)
     116 + // to enable automatic control of the switch,
     117 + // call the following method
     118 + // RX enable: 4
     119 + // TX enable: 5
     120 +
     121 + radio.setRfSwitchPins(16, 15);
     122 +
     123 +}
     124 + 
     125 +// flag to indicate that a packet was received
     126 +volatile bool receivedFlag = false;
     127 + 
     128 +// disable interrupt when it's not needed
     129 +volatile bool enableInterrupt = true;
     130 + 
     131 +// this function is called when a complete packet
     132 +// is received by the module
     133 +// IMPORTANT: this function MUST be 'void' type
     134 +// and MUST NOT have any arguments!
     135 +void setFlag(void) {
     136 + // check if the interrupt is enabled
     137 + if(!enableInterrupt) {
     138 + return;
     139 + }
     140 + 
     141 + // we got a packet, set the flag
     142 + receivedFlag = true;
     143 +}
     144 + 
     145 +void loop() {
     146 + 
     147 + SCmd.readSerial(); // We don't do much, just process serial commands
     148 + 
     149 + // check if the flag is set
     150 + if(receivedFlag && rx_status) {
     151 + // disable the interrupt service routine while
     152 + // processing the data
     153 + enableInterrupt = false;
     154 + 
     155 + // reset flag
     156 + receivedFlag = false;
     157 + 
     158 + // you can read received data as an Arduino String
     159 + String str;
     160 + int state = radio.readData(str);
     161 + 
     162 + // you can also read received data as byte array
     163 + /*
     164 + byte byteArr[8];
     165 + int state = radio.readData(byteArr, 8);
     166 + */
     167 + 
     168 + if (state == ERR_NONE) {
     169 + // packet was successfully received
     170 + Serial.println(F("[SX1262] Received packet!"));
     171 + 
     172 + // print data of the packet
     173 + Serial.print(F("[SX1262] Data:\t\t"));
     174 + Serial.println(str);
     175 + 
     176 + // print RSSI (Received Signal Strength Indicator)
     177 + Serial.print(F("[SX1262] RSSI:\t\t"));
     178 + Serial.print(radio.getRSSI());
     179 + Serial.println(F(" dBm"));
     180 + 
     181 + // print SNR (Signal-to-Noise Ratio)
     182 + Serial.print(F("[SX1262] SNR:\t\t"));
     183 + Serial.print(radio.getSNR());
     184 + Serial.println(F(" dB"));
     185 + 
     186 + } else if (state == ERR_CRC_MISMATCH) {
     187 + // packet was received, but is malformed
     188 + Serial.println(F("CRC error!"));
     189 + 
     190 + } else {
     191 + // some other error occurred
     192 + Serial.print(F("failed, code "));
     193 + Serial.println(state);
     194 + 
     195 + }
     196 + 
     197 + // put module back to listen mode
     198 + radio.startReceive();
     199 + 
     200 + // we're ready to receive more packets,
     201 + // enable interrupt service routine
     202 + enableInterrupt = true;
     203 + }
     204 + 
     205 +}
     206 +void help(){
     207 + Serial.println("Fw version: " + String(fwVersion,1)+"v");
     208 + Serial.println("\tConfiguration commands:");
     209 + Serial.println("\tset_rx");
     210 + Serial.println("\tset_tx");
     211 + Serial.println("\tset_tx_hex");
     212 + Serial.println("\tset_tx_ascii");
     213 + Serial.println("\tset_chann");
     214 + Serial.println("\tset_freq");
     215 + Serial.println("\tset_sf");
     216 + Serial.println("\tset_bw");
     217 + Serial.println("\tset_cr");
     218 + Serial.println("\tset_sw");
     219 + Serial.println("\tset_pl");
     220 + Serial.println("\tset_op");
     221 + 
     222 + Serial.println("Monitor commands:");
     223 + Serial.println("\tget_freq");
     224 + Serial.println("\tget_sf");
     225 + Serial.println("\tget_bw");
     226 + Serial.println("\tget_cr");
     227 + Serial.println("\tget_sw");
     228 + Serial.println("\tget_pl");
     229 + Serial.println("\tget_op");
     230 + Serial.println("\tget_config");
     231 + 
     232 + Serial.println("..help");
     233 + 
     234 +}
     235 + 
     236 +void set_tx0(){
     237 + char *arg;
     238 + byte data[64];
     239 + int i;
     240 + 
     241 + arg = SCmd.next(); // Get the next argument from the SerialCommand object buffer
     242 + if(arg != NULL){
     243 + for(i = 0; arg != NULL; i++){
     244 + if((arg[0] > 47 && arg[0]< 58) && (arg[1] > 47 && arg[1]< 58) && (arg[2] > 47 && arg[2]< 58) && arg[3] == 0){
     245 +
     246 + data[i] = (byte)strtoul(arg, NULL, 10);
     247 + //Serial.println(data[i],BIN);
     248 + }
     249 + else {
     250 + Serial.println("Use a series of xxx values separated by spaces. The value xxx represents a 3-digit number < 255. ");
     251 + return;
     252 + }
     253 + arg = SCmd.next(); // Get the next argument from the SerialCommand object buffer
     254 + }
     255 +
     256 + for(int j = 0; j < i; j++){
     257 + Serial.print(data[j]);
     258 + Serial.print(" ");
     259 + }
     260 +
     261 + int state = radio.transmit(data, i);
     262 + 
     263 + if (state == ERR_NONE) {
     264 + // the packet was successfully transmitted
     265 + Serial.println(F("success!"));
     266 +
     267 + // print measured data rate
     268 + Serial.print(F("[SX1262] Datarate:\t"));
     269 + Serial.print(radio.getDataRate());
     270 + Serial.println(F(" bps"));
     271 +
     272 + } else if (state == ERR_PACKET_TOO_LONG) {
     273 + // the supplied packet was longer than 256 bytes
     274 + Serial.println(F("too long!"));
     275 +
     276 + } else if (state == ERR_TX_TIMEOUT) {
     277 + // timeout occured while transmitting packet
     278 + Serial.println(F("timeout!"));
     279 +
     280 + } else {
     281 + // some other error occurred
     282 + Serial.print(F("failed, code "));
     283 + Serial.println(state);
     284 +
     285 + }
     286 + 
     287 + Serial.println();
     288 + Serial.print(i);
     289 + Serial.println(" byte(s) sent");
     290 +
     291 + rx_status = false;
     292 + }
     293 + else {
     294 + Serial.println("No argument");
     295 + }
     296 +}
     297 + 
     298 +void set_tx1(){
     299 + char *arg;
     300 + byte data[64];
     301 + int i;
     302 + 
     303 + arg = SCmd.next(); // Get the next argument from the SerialCommand object buffer
     304 + if(arg != NULL){
     305 + for(i = 0; arg != NULL; i++){
     306 +
     307 + if((arg[0] > 64 && arg[0]< 71 || arg[0] > 47 && arg[0]< 58) && (arg[1] > 64 && arg[1]< 71 || arg[1] > 47 && arg[1]< 58) && arg[2] == 0){
     308 +
     309 + data[i] = 0;
     310 + data[i] = nibble(*(arg))<<4;
     311 + data[i] = data[i]|nibble(*(arg + 1));
     312 + }
     313 + else{
     314 + Serial.println("Use a series of yy values separated by spaces. The value yy represents any pair of hexadecimal digits. ");
     315 + return;
     316 + }
     317 + arg = SCmd.next(); // Get the next argument from the SerialCommand object buffer
     318 + }
     319 +
     320 + for(int j = 0; j < i; j++){
     321 + Serial.print(data[j], HEX);
     322 + Serial.print(" ");
     323 + }
     324 + 
     325 + int state = radio.transmit(data, i);
     326 + 
     327 + if (state == ERR_NONE) {
     328 + // the packet was successfully transmitted
     329 + Serial.println(F("success!"));
     330 +
     331 + // print measured data rate
     332 + Serial.print(F("[SX1262] Datarate:\t"));
     333 + Serial.print(radio.getDataRate());
     334 + Serial.println(F(" bps"));
     335 +
     336 + } else if (state == ERR_PACKET_TOO_LONG) {
     337 + // the supplied packet was longer than 256 bytes
     338 + Serial.println(F("too long!"));
     339 +
     340 + } else if (state == ERR_TX_TIMEOUT) {
     341 + // timeout occured while transmitting packet
     342 + Serial.println(F("timeout!"));
     343 +
     344 + } else {
     345 + // some other error occurred
     346 + Serial.print(F("failed, code "));
     347 + Serial.println(state);
     348 +
     349 + }
     350 + 
     351 +
     352 + Serial.println();
     353 + Serial.print(i);
     354 + Serial.println(" byte(s) sent");
     355 +
     356 + rx_status = false;
     357 + 
     358 + }
     359 + else {
     360 + Serial.println("No argument");
     361 + }
     362 +}
     363 + 
     364 + 
     365 +void set_tx2(){
     366 + char *arg;
     367 + arg = SCmd.next(); // Get the next argument from the SerialCommand object buffer
     368 + if (arg != NULL){
     369 +
     370 + Serial.print(F("[SX1262] Transmitting packet ... "));
     371 +
     372 + // you can transmit C-string or Arduino string up to
     373 + // 256 characters long
     374 + // NOTE: transmit() is a blocking method!
     375 + // See example SX126x_Transmit_Interrupt for details
     376 + // on non-blocking transmission method.
     377 + int state = radio.transmit("Hello World!");
     378 +
     379 + // you can also transmit byte array up to 256 bytes long
     380 + /*
     381 + byte byteArr[] = {0x01, 0x23, 0x45, 0x56, 0x78, 0xAB, 0xCD, 0xEF};
     382 + int state = radio.transmit(byteArr, 8);
     383 + */
     384 +
     385 + if (state == ERR_NONE) {
     386 + // the packet was successfully transmitted
     387 + Serial.println(F(" Success!, ASCII message sent"));
     388 +
     389 + // print measured data rate
     390 + Serial.print(F("[SX1262] Datarate:\t"));
     391 + Serial.print(radio.getDataRate());
     392 + Serial.println(F(" bps"));
     393 +
     394 + } else if (state == ERR_PACKET_TOO_LONG) {
     395 + // the supplied packet was longer than 256 bytes
     396 + Serial.println(F("too long!"));
     397 +
     398 + } else if (state == ERR_TX_TIMEOUT) {
     399 + // timeout occured while transmitting packet
     400 + Serial.println(F("timeout!"));
     401 +
     402 + } else {
     403 + // some other error occurred
     404 + Serial.print(F("failed, code "));
     405 + Serial.println(state);
     406 +
     407 + }
     408 +
     409 + rx_status = false;
     410 + }
     411 + else {
     412 + Serial.println("No argument");
     413 + }
     414 +}
     415 + 
     416 +void set_freq(){
     417 + char *arg;
     418 + arg = SCmd.next();
     419 + frequency = atof(arg);
     420 + if (arg != NULL){
     421 + if(frequency > 902 && frequency < 923){
     422 + 
     423 + if (radio.setFrequency(frequency) == ERR_INVALID_FREQUENCY) {
     424 + Serial.println(F("Selected frequency is invalid for this module!"));
     425 + return;
     426 + }
     427 +
     428 + Serial.println("Frequency set to " + String(frequency) + " MHz");
     429 + rx_status = false;
     430 + }
     431 + else{
     432 + Serial.println("Error setting the frequency");
     433 + Serial.println("Value must be between 902 MHz and 923 MHz");
     434 + }
     435 + }
     436 + else {
     437 + Serial.println("No argument");
     438 + }
     439 +}
     440 + 
     441 +void set_chann(){
     442 + char *arg;
     443 + arg = SCmd.next(); // Get the next argument from the SerialCommand object buffer
     444 + channel = atoi(arg);
     445 + if (arg != NULL){
     446 + if(channel > -1 && channel < 64){
     447 + long freq = 902300000 + channel*125000;
     448 + frequency = (float)freq/1000000;
     449 +
     450 + //LoRa.setFrequency(freq);
     451 + if (radio.setFrequency(frequency) == ERR_INVALID_FREQUENCY) {
     452 + Serial.println(F("Selected frequency is invalid for this module!"));
     453 + return;
     454 + }
     455 +
     456 + Serial.println("Uplink channel set to " + String(channel));
     457 + rx_status = false;
     458 + }
     459 + else if(channel > 63 && channel < 72){
     460 + long freq = 903000000 + (channel - 64)*500000;
     461 + frequency = (float)freq/1000000;
     462 + 
     463 + if (radio.setFrequency(frequency) == ERR_INVALID_FREQUENCY) {
     464 + Serial.println(F("Selected frequency is invalid for this module!"));
     465 + return;
     466 + }
     467 +
     468 + Serial.println("Downlink channel set to " + String(channel));
     469 + rx_status = false;
     470 + }
     471 + else{
     472 + Serial.println("Error setting the channel");
     473 + Serial.println("Value must be between 0 and 63");
     474 + }
     475 + }
     476 + else {
     477 + Serial.println("No argument");
     478 + }
     479 +}
     480 + 
     481 + 
     482 +void set_sf(){
     483 + char *arg;
     484 + arg = SCmd.next();
     485 + if (arg != NULL){
     486 + spreadFactor = atoi(arg);
     487 + if(spreadFactor < 6 || spreadFactor > 12){
     488 + Serial.println("Error setting the Spreading factor");
     489 + Serial.println("Value must be between 6 and 12");
     490 + return;
     491 + }
     492 + else{
     493 + 
     494 + if (radio.setSpreadingFactor(spreadFactor) == ERR_INVALID_SPREADING_FACTOR) {
     495 + Serial.println(F("Selected spreading factor is invalid for this module!"));
     496 + return;
     497 + }
     498 + Serial.println("Spreading factor set to " + String(spreadFactor));
     499 + rx_status = false;
     500 + }
     501 + 
     502 + }
     503 + else {
     504 + Serial.println("No argument");
     505 + }
     506 +}
     507 + 
     508 +void set_cr(){
     509 + char *arg;
     510 + arg = SCmd.next();
     511 + if (arg != NULL){
     512 + codingRate = atoi(arg);
     513 + if(codingRate > 8 || codingRate < 5){
     514 + Serial.println("Error setting the Coding Rate");
     515 + Serial.println("Value must be between 5 and 8");
     516 + return;
     517 + }
     518 + else{
     519 + 
     520 + if (radio.setCodingRate(codingRate) == ERR_INVALID_CODING_RATE) {
     521 + Serial.println(F("Selected coding rate is invalid for this module!"));
     522 + return;
     523 + }
     524 + Serial.println("CodingRate set to 4/" + String(codingRate));
     525 + rx_status = false;
     526 + }
     527 + 
     528 + }
     529 + else {
     530 + Serial.println("No argument");
     531 + }
     532 +}
     533 + 
     534 +void set_bw(){
     535 + char *arg;
     536 + arg = SCmd.next(); // Get the next argument from the SerialCommand object buffer
     537 + int bwRefResp = bwReference; //save the previous data
     538 + bwReference = atoi(arg);
     539 + if (arg != NULL){
     540 + switch (bwReference){
     541 + case 0:
     542 + if (radio.setBandwidth(7.8) == ERR_INVALID_BANDWIDTH) {
     543 + Serial.println(F("Selected bandwidth is invalid for this module!"));
     544 + return;
     545 + }
     546 + rx_status = false;
     547 + Serial.println("Bandwidth set to 7.8 kHz");
     548 + break;
     549 + case 1:
     550 + if (radio.setBandwidth(10.4) == ERR_INVALID_BANDWIDTH) {
     551 + Serial.println(F("Selected bandwidth is invalid for this module!"));
     552 + return;
     553 + }
     554 + rx_status = false;
     555 + Serial.println("Bandwidth set to 10.4 kHz");
     556 + break;
     557 + case 2:
     558 + //LoRa.setSignalBandwidth(15.6E3);
     559 + if (radio.setBandwidth(15.6) == ERR_INVALID_BANDWIDTH) {
     560 + Serial.println(F("Selected bandwidth is invalid for this module!"));
     561 + return;
     562 + }
     563 + rx_status = false;
     564 + Serial.println("Bandwidth set to 15.6 kHz");
     565 + break;
     566 + case 3:
     567 + if (radio.setBandwidth(20.8) == ERR_INVALID_BANDWIDTH) {
     568 + Serial.println(F("Selected bandwidth is invalid for this module!"));
     569 + return;
     570 + }
     571 + rx_status = false;
     572 + Serial.println("Bandwidth set to 20.8 kHz");
     573 + break;
     574 + case 4:
     575 + if (radio.setBandwidth(31.25) == ERR_INVALID_BANDWIDTH) {
     576 + Serial.println(F("Selected bandwidth is invalid for this module!"));
     577 + return;
     578 + }
     579 + rx_status = false;
     580 + Serial.println("Bandwidth set to 31.25 kHz");
     581 + break;
     582 + case 5:
     583 + if (radio.setBandwidth(41.7) == ERR_INVALID_BANDWIDTH) {
     584 + Serial.println(F("Selected bandwidth is invalid for this module!"));
     585 + return;
     586 + }
     587 + rx_status = false;
     588 + Serial.println("Bandwidth set to 41.7 kHz");
     589 + break;
     590 + case 6:
     591 + if (radio.setBandwidth(62.5) == ERR_INVALID_BANDWIDTH) {
     592 + Serial.println(F("Selected bandwidth is invalid for this module!"));
     593 + return;
     594 + }
     595 + rx_status = false;
     596 + Serial.println("Bandwidth set to 62.5 kHz");
     597 + break;
     598 + case 7:
     599 + if (radio.setBandwidth(125) == ERR_INVALID_BANDWIDTH) {
     600 + Serial.println(F("Selected bandwidth is invalid for this module!"));
     601 + return;
     602 + }
     603 + rx_status = false;
     604 + Serial.println("Bandwidth set to 125 kHz");
     605 + break;
     606 + case 8:
     607 + if (radio.setBandwidth(250.0) == ERR_INVALID_BANDWIDTH) {
     608 + Serial.println(F("Selected bandwidth is invalid for this module!"));
     609 + return;
     610 + }
     611 + rx_status = false;
     612 + Serial.println("Bandwidth set to 250 kHz");
     613 + break;
     614 + 
     615 + default:
     616 + Serial.println("Error setting the bandwidth value must be between 0-8");
     617 + bwReference = bwRefResp; //if there's no valid data restore previous value
     618 + break;
     619 + }
     620 + }
     621 + else {
     622 + Serial.println("No argument");
     623 + }
     624 +}
     625 + 
     626 +void set_op(){
     627 + char *arg;
     628 + arg = SCmd.next();
     629 + if (arg != NULL){
     630 + outputPower = atoi(arg);
     631 + if(outputPower > -16 || outputPower < 23){
     632 + Serial.println("Error setting the output power");
     633 + Serial.println("Value must be between -17 and 22 dBm");
     634 + return;
     635 + }
     636 + else{
     637 + 
     638 + if (radio.setOutputPower(outputPower) == ERR_INVALID_OUTPUT_POWER) {
     639 + Serial.println(F("Selected output power is invalid for this module!"));
     640 + return;
     641 + }
     642 + Serial.println("Output power set to " + String(outputPower));
     643 + rx_status = false;
     644 + }
     645 + 
     646 + }
     647 + else {
     648 + Serial.println("No argument");
     649 + }
     650 +}
     651 + 
     652 +byte nibble(char c)
     653 +{
     654 + if (c >= '0' && c <= '9')
     655 + return c - '0';
     656 + 
     657 + if (c >= 'a' && c <= 'f')
     658 + return c - 'a' + 10;
     659 + 
     660 + if (c >= 'A' && c <= 'F')
     661 + return c - 'A' + 10;
     662 + 
     663 + return 0; // Not a valid hexadecimal character
     664 +}
     665 + 
     666 +void set_sw(){
     667 + char *arg;
     668 + byte data;
     669 + int i;
     670 + 
     671 + arg = SCmd.next(); // Get the next argument from the SerialCommand object buffer
     672 + if(arg != NULL){
     673 +
     674 + if((arg[0] > 64 && arg[0]< 71 || arg[0] > 47 && arg[0]< 58) && (arg[1] > 64 && arg[1]< 71 || arg[1] > 47 && arg[1]< 58) && arg[2] == 0){
     675 +
     676 + data = 0;
     677 + data = nibble(*(arg))<<4;
     678 + data = data|nibble(*(arg + 1));
     679 + if (radio.setSyncWord(data) != ERR_NONE) {
     680 + Serial.println(F("Unable to set sync word!"));
     681 + return;
     682 + }
     683 + Serial.println("Sync word set to 0x" + String(data));
     684 + }
     685 + else{
     686 + Serial.println("Use yy value. The value yy represents any pair of hexadecimal digits. ");
     687 + return;
     688 + }
     689 + }
     690 + else {
     691 + Serial.println("No argument");
     692 + }
     693 +}
     694 + 
     695 +void set_pl(){
     696 + char *arg;
     697 + arg = SCmd.next();
     698 + if (arg != NULL){
     699 + preambleLength = atoi(arg);
     700 + if(preambleLength > -1 || preambleLength < 65536){
     701 + Serial.println("Error setting the preamble length");
     702 + Serial.println("Value must be between 0 and 65535");
     703 + return;
     704 + }
     705 + else{
     706 + 
     707 + if (radio.setPreambleLength(preambleLength) == ERR_INVALID_CODING_RATE) {
     708 + Serial.println(F("Selected preamble length is invalid for this module!"));
     709 + return;
     710 + }
     711 + 
     712 + Serial.println("Preamble length set to " + String(preambleLength));
     713 + rx_status = false;
     714 + }
     715 + 
     716 + }
     717 + else {
     718 + Serial.println("No argument");
     719 + }
     720 +}
     721 + 
     722 +void set_rx(){
     723 + char *arg;
     724 + arg = SCmd.next();
     725 + if (arg != NULL){
     726 + frequency = atof(arg);
     727 + if(frequency > 902 && frequency < 923){
     728 + 
     729 + if (radio.setFrequency(frequency) == ERR_INVALID_FREQUENCY) {
     730 + Serial.println(F("Selected frequency is invalid for this module!"));
     731 + return;
     732 + }
     733 + 
     734 + Serial.println("LoRa radio receiving at " + String(frequency) + " MHz");
     735 +
     736 + // start listening for LoRa packets
     737 + Serial.print(F("[SX1262] Starting to listen ... "));
     738 + int state = radio.startReceive();
     739 + if (state == ERR_NONE) {
     740 + Serial.println(F("success!"));
     741 + } else {
     742 + Serial.print(F("failed, code "));
     743 + Serial.println(state);
     744 + while (true);
     745 + }
     746 +
     747 + rx_status = true;
     748 + }
     749 + else{
     750 + Serial.println("Error setting the frequency");
     751 + Serial.println("Value must be between 902 MHz and 923 MHz");
     752 + }
     753 + }
     754 + else {
     755 + Serial.println("LoRa radio receiving at " + String(frequency) + " MHz");
     756 + // start listening for LoRa packets
     757 + Serial.print(F("[SX1262] Starting to listen ... "));
     758 + int state = radio.startReceive();
     759 + if (state == ERR_NONE) {
     760 + Serial.println(F("success!"));
     761 + } else {
     762 + Serial.print(F("failed, code "));
     763 + Serial.println(state);
     764 + while (true);
     765 + }
     766 + rx_status = true;
     767 + }
     768 +}
     769 + 
     770 +/**********Get information**************/
     771 + 
     772 +void get_freq(){
     773 + Serial.print("Frequency = ");
     774 + Serial.println(frequency);
     775 +}
     776 + 
     777 +void get_sf(){
     778 + Serial.print("Spreading factor = ");
     779 + Serial.println(spreadFactor);
     780 +}
     781 + 
     782 +void get_cr(){
     783 + Serial.print("Coding Rate = ");
     784 + Serial.println(codingRate);
     785 +}
     786 + 
     787 +void get_sw(){
     788 + Serial.print("Sync Word = 0x");
     789 + Serial.println(syncWord, HEX);
     790 +}
     791 + 
     792 +void get_pl(){
     793 + Serial.print("Preamble Length = ");
     794 + Serial.println(preambleLength);
     795 +}
     796 + 
     797 +void get_op(){
     798 + Serial.print("Output Power = ");
     799 + Serial.println(outputPower);
     800 +}
     801 + 
     802 +void get_bw(){
     803 + Serial.println("Bandwidth = ");
     804 + switch (bwReference){
     805 + case 0:
     806 + Serial.println("7.8 kHz");
     807 + break;
     808 + case 1:
     809 + Serial.println("10.4 kHz");
     810 + break;
     811 + case 2:
     812 + Serial.println("15.6 kHz");
     813 + break;
     814 + case 3:
     815 + Serial.println("20.8 kHz");
     816 + break;
     817 + case 4:
     818 + Serial.println("31.25 kHz");
     819 + break;
     820 + case 5:
     821 + Serial.println("41.7 kHz");
     822 + break;
     823 + case 6:
     824 + Serial.println("62.5 kHz");
     825 + break;
     826 + case 7:
     827 + Serial.println("125 kHz");
     828 + break;
     829 + case 8:
     830 + Serial.println("250 kHz");
     831 + break;
     832 + default:
     833 + Serial.println("Error setting the bandwidth value must be between 0-8");
     834 + break;
     835 + }
     836 +}
     837 + 
     838 +void get_config(){
     839 + Serial.println("\nRadio configurations: ");
     840 + Serial.println("Frequency = " + String(frequency) + " MHz");
     841 + Serial.print("Bandwidth = ");
     842 + switch (bwReference){
     843 + case 0:
     844 + Serial.println("7.8 kHz");
     845 + break;
     846 + case 1:
     847 + Serial.println("10.4 kHz");
     848 + break;
     849 + case 2:
     850 + Serial.println("15.6 kHz");
     851 + break;
     852 + case 3:
     853 + Serial.println("20.8 kHz");
     854 + break;
     855 + case 4:
     856 + Serial.println("31.25 kHz");
     857 + break;
     858 + case 5:
     859 + Serial.println("41.7 kHz");
     860 + break;
     861 + case 6:
     862 + Serial.println("62.5 kHz");
     863 + break;
     864 + case 7:
     865 + Serial.println("125 kHz");
     866 + break;
     867 + case 8:
     868 + Serial.println("250 kHz");
     869 + break;
     870 + }
     871 + Serial.println("Spreading Factor = " + String(spreadFactor));
     872 + Serial.println("Coding Rate = 4/" + String(codingRate));
     873 + Serial.print("Sync Word = 0x");
     874 + Serial.println(syncWord, HEX);
     875 + Serial.println("Preamble Length = " + String(preambleLength));
     876 + Serial.println("Output Power = " + String(outputPower));
     877 + Serial.println("Rx active = " + String(rx_status));
     878 +}
     879 + 
     880 +// This gets set as the default handler, and gets called when no other command matches.
     881 +void unrecognized(const char *command) {
     882 + Serial.println("Command not found, type help to get the valid commands");
     883 +}
     884 + 
  • ■ ■ ■ ■ ■ ■
    firmware/pycatsniffer/README.md
     1 +pycatsniffer
     2 +============
     3 + 
     4 +*Live Packet Sniffer to Wireshark bridge for IEEE 802.15.4 networks.*
     5 + 
     6 +NOTE WELL: at the moment **pycatsniffer** only supports packet capture of the following protocols:
     7 +Bluetooth Low Energy Adversiting Channels (does not support decryption of encrypted packets)
     8 + 
     9 +A Python module that uses a **CatSniffer** (TI CC1352 chip) to sniff packets and pipe them to (primarily) wireshark.
     10 + 
     11 +This tool is a mashup of three existing GitHub projects:
     12 + 
     13 + * **[ccsniffpiper](https://github.com/andrewdodd/ccsniffpiper)**: A python tool by Andrew Dodd, based on the two below, that pipes the captured frames to wireshark.
     14 + * **[sensniff](https://github.com/g-oikonomou/sensniff)**: A python tool by George Oikonomou to capture packets with the "sensniff" firmware for the TI CC2531 sniffer.
     15 + * **[ccsniffer](https://github.com/christianpanton/ccsniffer)**: A python module by Christian Panton to capture packets with the original TI firmware and print them to stdout.
     16 + 
     17 +This tool is intended to be an alternative to the Windows-based SmartRF Packet Sniffer 2 program using TI's default firmware on CC13XX chips (and combine it with Wireshark's live capture utility). **pycatsniffer** has been developed on Linux.
     18 + 
     19 +Requires: pyserial
     20 + 
     21 +**pycatsniffer** can run in interactive or headless mode. In interactive mode, the user can change the radio channel while running.
     22 + 
     23 +How to Use
     24 +==========
     25 +Run pycatsniffer
     26 +----------------
     27 +**pycatsniffer**'s main role it to read packets captured from the CatSniffer board and pipe the packets in PCAP format to a named pipe (by default "/tmp/ccsniffpiper").
     28 + 
     29 +To get this default behaviour, just run the command:
     30 +`python pycatsniffer.py`
     31 + 
     32 +To see further information, run the help command:
     33 +`python pycatsniffer.py -h`
     34 + 
     35 +To run in headless mode and pipe using /tmp/ccsniffpiper
     36 +`sudo python pycatsniffer.py -d -f /tmp/ccsniffpiper`
     37 + 
     38 + 
     39 +Run Wireshark
     40 +-------------
     41 +To receive the packets from **pycatsniffer** you need to use Wireshark to start a capture using a FIFO file as the 'interface'. By default, **pycatsniffer** will use `/tmp/ccsniffpiper`.
     42 + 
     43 +To setup Wireshark correctly, perform the following steps:
     44 + * Go to Capture -> options -> Manage Interfaces -> New (under Pipes) -> type `/tmp/ccsniffpiper` and save.
     45 + * The pipe will then appear as an interface. Start a capture on it.
     46 + 
     47 + 
     48 +General packet format
     49 +======================================
     50 +This is just * **[documentation](https://software-dl.ti.com/lprf/packet_sniffer_2/docs/user_guide/html/sniffer_fw/firmware/command_interface.html)** of the packet format from the TI firmware on CatSniffer.
     51 + 
     52 +The UART packet format is shown in the table below.
     53 + 
     54 + 0 1 2 3 4 5 6 7 -2 -1 EOF
     55 + |_______|_______|_______|_______|_______|_______|_______|>> ... |_______|_______|_______|
     56 + |Start of Frame |Packet Packet Length |Payload >> | FCS | End of Frame|
     57 + | |Info | | | | |
     58 + 2B 1B 2B 0-2049B 1B 2B
     59 +
     60 +FAQs
     61 +====
     62 +### I don't see anything appearing in Wireshark!
     63 + 
     64 + * Check that the sniffer is sniffing in the correct channel.
     65 + * Check that you have opened the named pipe that is being piped to.
     66 + *In particular, I would recommend reading the "Run Wireshark" section carefully.*
     67 + 
     68 + 
  • ■ ■ ■ ■ ■ ■
    firmware/pycatsniffer/pingcatsniffer.py
     1 +import serial
     2 +import time
     3 +import sys
     4 + 
     5 +ser = serial.Serial()
     6 +ser.port = '/dev/ttyACM0'
     7 +ser.baudrate = 921600
     8 +ser.bytesize = serial.EIGHTBITS
     9 +ser.parity = serial.PARITY_NONE
     10 +ser.stopbits = serial.STOPBITS_ONE
     11 +ser.timeout = 1
     12 + 
     13 +ping = bytearray([0x40, 0x53, 0x40, 0x00, 0x00, 0x40, 0x40, 0x45])
     14 + 
     15 +time.sleep(1)
     16 +ser.open()
     17 + 
     18 +ser.write(cmd)
     19 + 
     20 +while(ser.in_waiting == 0):
     21 + pass
     22 +
     23 +time.sleep(0.01)
     24 +
     25 +if ser.in_waiting > 0:
     26 + msg = ser.read(ser.in_waiting)
     27 + print (msg.hex())
     28 +
     29 +ser.flush()
     30 +time.sleep(2)
     31 + 
     32 +ser.close()
     33 + 
     34 + 
  • ■ ■ ■ ■ ■ ■
    firmware/pycatsniffer/pycatsniffer.py
     1 +#!/usr/bin/env python
     2 +"""
     3 + pycatsniffer - a python module to connect to the CatSniffer
     4 + and pipe the sniffed packets to wireshark!
     5 + 
     6 + Copyright (c) 2023, Raul Vargas ([email protected])
     7 + 2013, Andrew Dodd ([email protected])
     8 +
     9 + 
     10 + This is essentially a mashup and extension of three existing sniffers:
     11 + 1. ccsniffpiper.py
     12 + ------------
     13 + Copyright (c) 2013, Andrew Dodd ([email protected])
     14 + 2. ccsniffer
     15 + ------------
     16 + Copyright (c) 2012, George Oikonomou ([email protected])
     17 + 3. sensniffer
     18 + -------------
     19 + Copyright (C) 2012 Christian Panton <[email protected]>
     20 + This program is free software; you can redistribute it and/or modify
     21 + it under the terms of the GNU General Public License as published by
     22 + the Free Software Foundation; either version 3 of the License, or
     23 + (at your option) any later version.
     24 + This program is distributed in the hope that it will be useful,
     25 + but WITHOUT ANY WARRANTY; without even the implied warranty of
     26 + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     27 + GNU General Public License for more details.
     28 + You should have received a copy of the GNU General Public License
     29 + along with this program; if not, write to the Free Software Foundation,
     30 + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
     31 +"""
     32 +"""
     33 + Functionality
     34 + -------------
     35 + Read IEEE802.15.4 frames from the default CC1352 sniffer firmware
     36 + and pipe them to wireshark via a FIFO/named pipe. At the same time, the
     37 + frames can be logged to a file for subsequent offline processing.
     38 + In interactive mode, the user can also input commands from stdin.
     39 +"""
     40 + 
     41 +import serial
     42 +import argparse
     43 +import binascii
     44 +import errno
     45 +import io
     46 +import logging.handlers
     47 +import os
     48 +import select
     49 +import stat
     50 +import struct
     51 +import sys
     52 +import threading
     53 +import time
     54 + 
     55 +__version__ = '0.0.1'
     56 + 
     57 +defaults = {
     58 + 'hex_file': 'ccsniffpiper.hexdump',
     59 + 'out_fifo': '/tmp/ccsniffpiper',
     60 + 'pcap_file': 'ccsniffpiper.pcap',
     61 + 'debug_level': 'WARNING',
     62 + 'log_level': 'INFO',
     63 + 'log_file': 'ccsniffpiper.log',
     64 + 'channel': 37,
     65 + 'initiator_address':0x000000000000,
     66 + 'port':'/dev/ttyACM0'
     67 +}
     68 + 
     69 +logger = logging.getLogger(__name__)
     70 +stats = {}
     71 + 
     72 +ping = bytearray([0x40, 0x53, 0x40, 0x00, 0x00, 0x40, 0x40, 0x45])
     73 +stop = bytearray([0x40, 0x53, 0x42, 0x00, 0x00, 0x42, 0x40, 0x45])
     74 +cfgphy = bytearray([0x40, 0x53, 0x47, 0x01, 0x00, 0x09, 0x51, 0x40, 0x45])
     75 +cfgfreq = bytearray([0x40, 0x53, 0x45, 0x04, 0x00, 0x62, 0x09, 0x00, 0x00, 0xb4, 0x40, 0x45])
     76 +initiator = bytearray([0x40, 0x53, 0x70, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x40, 0x45])
     77 +letsgo = bytearray([0x40, 0x53, 0x41, 0x00, 0x00, 0x41, 0x40, 0x45])
     78 + 
     79 +# channel:<frequency
     80 +channeldict = {
     81 + 37: [0x62, 0x09],
     82 + 38: [0x7a, 0x09],
     83 + 39: [0xb0, 0x09]
     84 +}
     85 + 
     86 +class Frame(object):
     87 + PCAP_FRAME_HDR_FMT = '<LLLL'
     88 + 
     89 + def __init__(self, macPDUByteArray, timestampBy32):
     90 + self.__macPDUByteArray = macPDUByteArray
     91 + self.timestampBy32 = timestampBy32
     92 + self.timestampUsec = timestampBy32 / 32.0
     93 + self.len = len(self.__macPDUByteArray)
     94 + 
     95 + self.__pcap_hdr = self.__generate_frame_hdr()
     96 + 
     97 + self.pcap = self.__pcap_hdr + self.__macPDUByteArray
     98 + self.hex = ''.join('%02x ' % c
     99 + for c in self.__macPDUByteArray).rstrip()
     100 + 
     101 + def __generate_frame_hdr(self):
     102 + sec = int(self.timestampUsec / 1000000)
     103 + usec = int(self.timestampUsec - sec)
     104 + return struct.pack(Frame.PCAP_FRAME_HDR_FMT, sec, usec, self.len,
     105 + self.len)
     106 + 
     107 + def get_pcap(self):
     108 + return self.pcap
     109 + 
     110 + def get_hex(self):
     111 + return self.hex
     112 + 
     113 + def get_timestamp(self):
     114 + return self.timestampUsec
     115 + 
     116 + 
     117 +#####################################
     118 + 
     119 + 
     120 +class PCAPHelper:
     121 + LINKTYPE_IEEE802_15_4_NOFCS = 230
     122 + LINKTYPE_IEEE802_15_4 = 195
     123 + MAGIC_NUMBER = 0xA1B2C3D4
     124 + VERSION_MAJOR = 2
     125 + VERSION_MINOR = 4
     126 + THISZONE = 0
     127 + SIGFIGS = 0
     128 + SNAPLEN = 0xFFFF
     129 + NETWORK = LINKTYPE_IEEE802_15_4
     130 + 
     131 + PCAP_GLOBAL_HDR_FMT = '<LHHlLLL'
     132 + 
     133 + @staticmethod
     134 + def writeGlobalHeader():
     135 + return struct.pack(PCAPHelper.PCAP_GLOBAL_HDR_FMT,
     136 + PCAPHelper.MAGIC_NUMBER, PCAPHelper.VERSION_MAJOR,
     137 + PCAPHelper.VERSION_MINOR, PCAPHelper.THISZONE,
     138 + PCAPHelper.SIGFIGS, PCAPHelper.SNAPLEN,
     139 + PCAPHelper.NETWORK)
     140 + 
     141 + 
     142 +class FifoHandler(object):
     143 + def __init__(self, out_fifo):
     144 + self.out_fifo = out_fifo
     145 + self.of = None
     146 + self.needs_pcap_hdr = True
     147 + self.thread = None
     148 + self.running = False
     149 + stats['Piped'] = 0
     150 + stats['Not Piped'] = 0
     151 + self.__create_fifo()
     152 + self.__start()
     153 + 
     154 + def __del__(self):
     155 + self.__stop()
     156 + 
     157 + def __start(self):
     158 + logger.debug("start FIFO watcher thread")
     159 + self.running = True
     160 + self.thread = threading.Thread(target=self.__fifo_watcher)
     161 + self.thread.daemon = True
     162 + self.thread.start()
     163 + 
     164 + def __stop(self):
     165 + logger.debug("stop FIFO watcher thread")
     166 + self.running = False
     167 + self.thread.join()
     168 + 
     169 + def __fifo_watcher(self):
     170 + while self.running:
     171 + self.__open_fifo(keepalive=True)
     172 + time.sleep(0.01)
     173 + 
     174 + def __create_fifo(self):
     175 + try:
     176 + os.mkfifo(self.out_fifo)
     177 + logger.info(f'Opened FIFO {self.out_fifo}')
     178 + except OSError as e:
     179 + if e.errno == errno.EEXIST:
     180 + if stat.S_ISFIFO(os.stat(self.out_fifo).st_mode) is False:
     181 + logger.error(
     182 + 'File {self.out_fifo} exists and is not a FIFO')
     183 + sys.exit(1)
     184 + else:
     185 + logger.warning(f'FIFO {self.out_fifo} exists. Using it')
     186 + else:
     187 + raise
     188 + 
     189 + def __open_fifo(self, keepalive=False):
     190 + try:
     191 + fd = os.open(self.out_fifo, os.O_NONBLOCK | os.O_WRONLY)
     192 + self.of = os.fdopen(fd, 'wb')
     193 + except OSError as e:
     194 + if e.errno == errno.ENXIO:
     195 + if not keepalive:
     196 + logger.warning('Remote end not reading')
     197 + stats['Not Piped'] += 1
     198 + self.of = None
     199 + self.needs_pcap_hdr = True
     200 + else:
     201 + raise
     202 + 
     203 + def triggerNewGlobalHeader(self):
     204 + self.needs_pcap_hdr = True
     205 + 
     206 + def handle(self, data):
     207 + if self.of is None:
     208 + self.__open_fifo()
     209 + 
     210 + if self.of is not None:
     211 + try:
     212 + if self.needs_pcap_hdr is True:
     213 + logger.info('Write global PCAP header')
     214 + self.of.write(PCAPHelper.writeGlobalHeader())
     215 + self.needs_pcap_hdr = False
     216 + self.of.write(data.pcap)
     217 + self.of.flush()
     218 + logger.debug(f'Wrote a frame of size {data.len} bytes')
     219 + stats['Piped'] += 1
     220 + except IOError as e:
     221 + if e.errno == errno.EPIPE:
     222 + logger.info('Remote end stopped reading')
     223 + stats['Not Piped'] += 1
     224 + self.of = None
     225 + self.needs_pcap_hdr = True
     226 + else:
     227 + raise
     228 + 
     229 + 
     230 +#####################################
     231 +class PcapDumpHandler(object):
     232 + def __init__(self, filename):
     233 + self.filename = filename
     234 + stats['Dumped to PCAP'] = 0
     235 + 
     236 + try:
     237 + self.of = open(self.filename, 'wb')
     238 + self.of.write(PCAPHelper.writeGlobalHeader())
     239 + logger.info(f'Dumping PCAP to {self.filename}')
     240 + except IOError as e:
     241 + self.of = None
     242 + logger.warning(
     243 + f'Error opening {self.filename} to save pcap. Skipping')
     244 + logger.warning(f'The error was: {e.args}')
     245 + 
     246 + def handle(self, frame):
     247 + if self.of is None:
     248 + return
     249 + self.of.write(frame.get_pcap())
     250 + self.of.flush()
     251 + logger.info(
     252 + f'PcapDumpHandler: Dumped a frame of size {frame.len} bytes')
     253 + stats['Dumped to PCAP'] += 1
     254 + 
     255 + 
     256 +class HexdumpHandler(object):
     257 + def __init__(self, filename):
     258 + self.filename = filename
     259 + stats['Dumped as Hex'] = 0
     260 + try:
     261 + self.of = open(self.filename, 'wb')
     262 + logger.info(f'Dumping hex to {self.filename}')
     263 + except IOError as e:
     264 + logger.warning(
     265 + f'Error opening {self.filename} for hex dumps. Skipping')
     266 + logger.warning(f'The error was: {e.args}')
     267 + self.of = None
     268 + 
     269 + def handle(self, frame):
     270 + if self.of is None:
     271 + return
     272 + 
     273 + try:
     274 + # Prepend the original timestamp in big-endian format
     275 + self.of.write(
     276 + binascii.hexlify(
     277 + struct.pack(">I ", int(frame.get_timestamp() * 32))))
     278 + #self.of.write(str(frame.get_timestamp()))
     279 + self.of.write(bytes(" ", 'ascii'))
     280 + # self.of.write('0000 ')
     281 + self.of.write(bytes(frame.get_hex(), 'ascii'))
     282 + self.of.write(bytes('\n', 'ascii'))
     283 + self.of.flush()
     284 + stats['Dumped as Hex'] += 1
     285 + logger.info(
     286 + f'HexdumpHandler: Dumped a frame of size {frame.len} bytes')
     287 + except IOError as e:
     288 + logger.warning(
     289 + f'Error writing hex to {self.of} for hex dumps. Skipping')
     290 + logger.warning(f'The error was: {e.args}')
     291 + 
     292 + 
     293 + 
     294 + 
     295 +class CC1352:
     296 + 
     297 + DEFAULT_CHANNEL = 0x25 # 371
     298 + 
     299 + START_FRAME = 0x5340
     300 + 
     301 + def __init__(self, port, callback, channel=DEFAULT_CHANNEL):
     302 + 
     303 + baudrate = 921600
     304 + rts_cts = False
     305 +
     306 + stats['Captured'] = 0
     307 + stats['Non-Frame'] = 0
     308 + 
     309 + self.serial_port = None
     310 + self.channel = channel
     311 + self.callback = callback
     312 + self.thread = None
     313 + self.running = False
     314 +
     315 + try:
     316 + #self.serial_port = serial.Serial(port, baudrate, 8, 'N', 1, timeout=1)
     317 + self.serial_port = serial.Serial(port = port,
     318 + baudrate = baudrate,
     319 + bytesize = serial.EIGHTBITS,
     320 + parity = serial.PARITY_NONE,
     321 + stopbits = serial.STOPBITS_ONE,
     322 + xonxoff = False,
     323 + rtscts = rts_cts,
     324 + timeout = 0.1)
     325 + self.serial_port.flushInput()
     326 + self.serial_port.flushOutput()
     327 + except (serial.SerialException, ValueError, IOError, OSError) as e:
     328 + logger.error('Error opening port: %s' % (port,))
     329 + logger.error('The error was: %s' % (e.args,))
     330 + sys.exit(1)
     331 + logger.info('Serial port %s opened' % (self.serial_port.name))
     332 +
     333 + def close(self):
     334 + self.serial_port.close()
     335 + 
     336 + def pingc(self):
     337 + self.serial_port.write(ping)
     338 +
     339 + def stopc(self):
     340 + self.serial_port.write(stop)
     341 +
     342 + def cfgphyc(self):
     343 + self.serial_port.write(cfgphy)
     344 +
     345 + def cfgfreqc(self):
     346 + self.serial_port.write(cfgfreq)
     347 + 
     348 + def initiatorc(self):
     349 + self.serial_port.write(initiator)
     350 + 
     351 + def startc(self):
     352 + # start sniffing
     353 + self.running = True
     354 +
     355 + self.serial_port.write(letsgo)
     356 +
     357 + self.thread = threading.Thread(target=self.recv)
     358 + #self.thread.daemon = True
     359 + self.thread.start()
     360 + 
     361 + def stop(self):
     362 + # end sniffing
     363 + self.serial_port.write(stop)
     364 + self.running = False
     365 + self.thread.join()
     366 + 
     367 + def isRunning(self):
     368 + return self.running
     369 + 
     370 + 
     371 + def recv(self):
     372 +
     373 + while self.running:
     374 + if self.serial_port.in_waiting > 0:
     375 + bytestream = self.serial_port.read(self.serial_port.in_waiting)
     376 +
     377 + #print ("RECV>> %s" % binascii.hexlify(bytestream))
     378 + 
     379 + time.sleep(0.5)
     380 + start_index = 0
     381 +
     382 + while True:
     383 + # Find the index of the next occurrence of 0x40 0x53
     384 + start_index = bytestream.find(b'\x40\x53', start_index)
     385 + # If not found, break out of the loop
     386 + if start_index == -1:
     387 + break
     388 + # Find the index of the next occurrence of 0x40 0x45 after the start index
     389 + end_index = bytestream.find(b'\x40\x45', start_index)
     390 + # If not found, break out of the loop
     391 + if end_index == -1:
     392 + break
     393 + # Get the substream between start_index and end_index
     394 + substream = bytestream[start_index:end_index+2]
     395 +
     396 + # Do something with the substream
     397 + #print(substream)
     398 + 
     399 + print ("RECV>> %s" % binascii.hexlify(substream))
     400 + 
     401 + if len(substream) >= 3:
     402 + (sFrame, pInfo, pLength) = struct.unpack_from("<HBH", substream)
     403 + payload = substream[5:-2]
     404 + #print ("sframe>>", hex(sFrame))
     405 + #print ("pInfo>>", hex(pInfo))
     406 + #print ("pLength>>", hex(pLength))
     407 + 
     408 + if len(payload) == pLength:
     409 + # buffer contains the correct number of bytes
     410 + if sFrame == CC1352.START_FRAME:
     411 + (timestamp,) = struct.unpack_from("<Q", payload)
     412 + timestamp = timestamp & 0x0000FFFFFFFFFFFF
     413 + 
     414 + frame = payload[6:]
     415 + self.callback(timestamp, frame)
     416 + else:
     417 + logger.warning(
     418 + 'Received a command response with unknown code - CMD:{:02x} byte:{}'
     419 + .format(cmd, substream))
     420 + 
     421 + # Set the start index to end_index + 2 (to skip over the 0x40 0x45 bytes)
     422 + start_index = end_index + 2
     423 +
     424 + 
     425 + def set_channel(self, channel):
     426 + was_running = self.running
     427 + 
     428 + if channel >= 37 and channel <= 39:
     429 + if self.running:
     430 + self.stop()
     431 + 
     432 + chann = channeldict[channel]
     433 + 
     434 + cfgfreq[5:7] = bytearray(chann)
     435 + 
     436 + #Add all bytes in these fields: Packet Info, Packet Length and Payload.
     437 + #AND the result from step 1 with 0xFF.
     438 + 
     439 + fcs = 0
     440 + for x in cfgfreq[2:-3]:
     441 + fcs = fcs + x
     442 + fcs = fcs & 0xFF
     443 + cfgfreq[-3] = fcs
     444 + 
     445 + self.channel = channel
     446 + 
     447 + self.get_channel()
     448 + 
     449 + if was_running:
     450 + self.startc()
     451 + 
     452 + else:
     453 + raise ValueError("Channel must be between 11 and 26")
     454 + 
     455 + def get_channel(self):
     456 + return self.channel
     457 + 
     458 +def arg_parser():
     459 + debug_choices = ('DEBUG', 'INFO', 'WARNING', 'ERROR')
     460 + 
     461 + parser = argparse.ArgumentParser(add_help=False,
     462 + description='Read IEEE802.15.4 frames \
     463 + from a CC1352 packet sniffer device, convert them to pcap and pipe them \
     464 + into wireshark over a FIFO pipe for online analysis. Frames \
     465 + can also be saved in a file in hexdump and/or pcap format for offline \
     466 + analysis.')
     467 + 
     468 + in_group = parser.add_argument_group('Input Options')
     469 + in_group.add_argument(
     470 + '-c',
     471 + '--channel',
     472 + type=int,
     473 + action='store',
     474 + choices=list(range(37, 40)),
     475 + default=defaults['channel'],
     476 + help='Set the sniffer\'s CHANNEL. Valid range: 37-39. \
     477 + (Default: %s)' % (defaults['channel'], ))
     478 + in_group.add_argument(
     479 + '-a',
     480 + '--address',
     481 + type=int,
     482 + action='store',
     483 + #choices=list(range(37, 40)),
     484 + default=defaults['initiator_address'],
     485 + help='Connect to Initiator Address. \
     486 + (Default: %s)' % (defaults['initiator_address'], ))
     487 + out_group = parser.add_argument_group('Output Options')
     488 + out_group.add_argument(
     489 + '-f',
     490 + '--fifo',
     491 + action='store',
     492 + default=defaults['out_fifo'],
     493 + help='Set FIFO as the named pipe for sending to wireshark. \
     494 + If argument is omitted and -o option is not specified \
     495 + the capture will pipe to: %s' %
     496 + (defaults['out_fifo'], ))
     497 + out_group.add_argument(
     498 + '-o',
     499 + '--offline',
     500 + action='store_true',
     501 + default=False,
     502 + help='Disables sending the capture to the named pipe.')
     503 + out_group.add_argument('-x',
     504 + '--hex-file',
     505 + action='store',
     506 + nargs='?',
     507 + const=defaults['hex_file'],
     508 + default=False,
     509 + help='Save the capture (hexdump) in HEX_FILE. \
     510 + If -x is specified but HEX_FILE is omitted, \
     511 + %s will be used. If the argument is \
     512 + omitted altogether, the capture will not \
     513 + be saved.' % (defaults['hex_file'], ))
     514 + out_group.add_argument('-p',
     515 + '--pcap-file',
     516 + action='store',
     517 + nargs='?',
     518 + const=defaults['pcap_file'],
     519 + default=False,
     520 + help='Save the capture (pcap format) in PCAP_FILE. \
     521 + If -p is specified but PCAP_FILE is omitted, \
     522 + %s will be used. If the argument is \
     523 + omitted altogether, the capture will not \
     524 + be saved.' % (defaults['pcap_file'], ))
     525 + 
     526 + log_group = parser.add_argument_group('Verbosity and Logging')
     527 + log_group.add_argument(
     528 + '-d',
     529 + '--headless',
     530 + action='store_true',
     531 + default=False,
     532 + help='Run in non-interactive/headless mode, without \
     533 + accepting user input. (Default Disabled)')
     534 + log_group.add_argument('-D',
     535 + '--debug-level',
     536 + action='store',
     537 + choices=debug_choices,
     538 + default=defaults['debug_level'],
     539 + help='Print messages of severity DEBUG_LEVEL \
     540 + or higher (Default %s)' %
     541 + (defaults['debug_level'], ))
     542 + log_group.add_argument('-L',
     543 + '--log-file',
     544 + action='store',
     545 + nargs='?',
     546 + const=defaults['log_file'],
     547 + default=False,
     548 + help='Log output in LOG_FILE. If -L is specified \
     549 + but LOG_FILE is omitted, %s will be used. \
     550 + If the argument is omitted altogether, \
     551 + logging will not take place at all.' %
     552 + (defaults['log_file'], ))
     553 + log_group.add_argument('-l',
     554 + '--log-level',
     555 + action='store',
     556 + choices=debug_choices,
     557 + default=defaults['log_level'],
     558 + help='Log messages of severity LOG_LEVEL or \
     559 + higher. Only makes sense if -L is also \
     560 + specified (Default %s)' %
     561 + (defaults['log_level'], ))
     562 + 
     563 + gen_group = parser.add_argument_group('General Options')
     564 + gen_group.add_argument('-v',
     565 + '--version',
     566 + action='version',
     567 + version='ccsniffpiper v%s' % (__version__))
     568 + gen_group.add_argument('-h',
     569 + '--help',
     570 + action='help',
     571 + help='Shows this message and exits')
     572 + 
     573 + return parser.parse_args()
     574 + 
     575 + 
     576 +def dump_stats():
     577 + s = io.StringIO()
     578 + 
     579 + s.write('Frame Stats:\n')
     580 + for k, v in list(stats.items()):
     581 + s.write('%20s: %d\n' % (k, v))
     582 + 
     583 + print((s.getvalue()))
     584 + 
     585 + 
     586 +def log_init():
     587 + logger.setLevel(logging.DEBUG)
     588 + ch = logging.StreamHandler()
     589 + ch.setLevel(getattr(logging, args.debug_level))
     590 + cf = logging.Formatter('%(message)s')
     591 + ch.setFormatter(cf)
     592 + logger.addHandler(ch)
     593 + 
     594 + if args.log_file is not False:
     595 + fh = logging.handlers.RotatingFileHandler(filename=args.log_file,
     596 + maxBytes=5000000)
     597 + fh.setLevel(getattr(logging, args.log_level))
     598 + ff = logging.Formatter('%(asctime)s - %(levelname)8s - %(message)s')
     599 + fh.setFormatter(ff)
     600 + logger.addHandler(fh)
     601 + 
     602 + 
     603 +if __name__ == '__main__':
     604 + args = arg_parser()
     605 + log_init()
     606 + 
     607 + logger.info('Started logging')
     608 + 
     609 + handlers = []
     610 + 
     611 + def handlerDispatcher(timestamp, macPDU):
     612 + """ Dispatches any received frames to all registered handlers
     613 + timestamp -> The timestamp the frame was received, as reported by the sniffer device, in microseconds
     614 + macPDU -> The 802.15.4 MAC-layer PDU, starting with the Frame Control Field (FCF)
     615 + """
     616 + if len(macPDU) > 0:
     617 + frame = Frame(macPDU, timestamp)
     618 + for h in handlers:
     619 + h.handle(frame)
     620 + 
     621 + if args.offline is not True:
     622 + f = FifoHandler(out_fifo=args.fifo)
     623 + handlers.append(f)
     624 + if args.hex_file is not False:
     625 + handlers.append(HexdumpHandler(args.hex_file))
     626 + if args.pcap_file is not False:
     627 + handlers.append(PcapDumpHandler(args.pcap_file))
     628 + 
     629 + if args.headless is False:
     630 + h = io.StringIO()
     631 + h.write('Commands:\n')
     632 + h.write('c: Print current RF Channel\n')
     633 + h.write('n: Trigger new pcap header before the next frame\n')
     634 + h.write('h,?: Print this message\n')
     635 + h.write('[37,39]: Change RF channel\n')
     636 + h.write('s: Start/stop the packet capture\n')
     637 + h.write('q: Quit')
     638 + h = h.getvalue()
     639 + 
     640 + e = 'Unknown Command. Type h or ? for help'
     641 + 
     642 + print(h)
     643 + 
     644 + snifferDev = CC1352(defaults['port'], handlerDispatcher, args.channel)
     645 + try:
     646 + 
     647 + while 1:
     648 + if args.headless is True:
     649 + if not snifferDev.isRunning():
     650 + 
     651 + snifferDev.pingc()
     652 + snifferDev.stopc()
     653 + snifferDev.cfgphyc()
     654 + snifferDev.cfgfreqc()
     655 + snifferDev.initiatorc()
     656 + snifferDev.startc()
     657 + print ("start")
     658 + 
     659 + # snifferDev.start()
     660 + # block until terminated (Ctrl+C or killed)
     661 + snifferDev.thread.join()
     662 + else:
     663 + try:
     664 + if select.select([
     665 + sys.stdin,
     666 + ], [], [], 10.0)[0]:
     667 + cmd = sys.stdin.readline().rstrip()
     668 + logger.debug(f'User input: "{cmd}"')
     669 + if cmd in ('h', '?'):
     670 + print(h)
     671 + elif cmd == 'c':
     672 + # We'll only ever see this if the user asked for it, so we are
     673 + # running interactive. Print away
     674 + print(
     675 + f'Sniffing in channel: {snifferDev.get_channel()}'
     676 + )
     677 + elif cmd == 'n':
     678 + f.triggerNewGlobalHeader()
     679 + elif cmd == 'q':
     680 + logger.info('User requested shutdown')
     681 + sys.exit(0)
     682 + elif cmd == 's':
     683 + if snifferDev.isRunning():
     684 + snifferDev.stop()
     685 + print("stop")
     686 + else:
     687 + snifferDev.pingc()
     688 + snifferDev.stopc()
     689 + snifferDev.cfgphyc()
     690 + snifferDev.cfgfreqc()
     691 + snifferDev.initiatorc()
     692 + snifferDev.startc()
     693 + print ("start")
     694 + elif int(cmd) in range(37, 40):
     695 + snifferDev.set_channel(int(cmd))
     696 + snifferDev.cfgfreqc()
     697 + #print(cfgfreq.hex())
     698 + else:
     699 + raise ValueError
     700 +# else:
     701 +# logger.debug('No user input')
     702 + except select.error:
     703 + logger.warning('Error while trying to read stdin')
     704 + except ValueError as e:
     705 + print(e)
     706 + except UnboundLocalError:
     707 + # Raised by command 'n' when -o was specified at command line
     708 + pass
     709 + 
     710 + except (KeyboardInterrupt, SystemExit):
     711 + logger.info('Shutting down')
     712 + if snifferDev.isRunning():
     713 + snifferDev.stop()
     714 + dump_stats()
     715 + sys.exit(0)
Please wait...
Page is in error, reload to recover