Save data to a list but the data is generated only when user enter an input

Hi folks. So I have been getting my head around this and could not find a solution.

So basically I have some barcodes coming from the Serial Barcode Scanner. When a barcode is scanned, my code searches through the if else to compare the scanned barcode with the existing one in the database. And this process is repeated until the user wishes to 'CHECKOUT'. I am doing fine until this stage. When the user wants to 'CHECKOUT', they just put a card on the reader and all the barcodes that have been scanned will be written on different block of the card. I am using MFRC522 RFID by the way.

The problem is that how I am suppose to write a code that only take the barcodes that have been scanned to be written on the card?

This is what I currently have

#include <SPI.h>
#include <MFRC522.h>

#define SS_PIN 7  
#define RST_PIN 6
MFRC522 mfrc522(SS_PIN, RST_PIN);       
MFRC522::MIFARE_Key key;

struct ItemList {
  char barcode[10];
  char name[25];
  unsigned int price;
};

void setup()
{
  Serial.begin(9600);      
  SPI.begin();               
  mfrc522.PCD_Init();   

  for (byte i = 0; i < 6; i++) {
    key.keyByte[i] = 0xFF;
  }
}

ItemList item1 = 
{
  "452795615",
  "SOME ITEM_1  $1.00",
  100
};
ItemList item2 = 
{
  "562330250",
  "SOME ITEM_2  $1.80",
  180
};

unsigned int block = 2;
byte codeBuffer[10];
const byte numChars = 12;
char receivedChars[numChars];

boolean newData = false;

unsigned int total = 0;


void loop()
{
  recvWithEndMarker();
  processData();
  writeDataToCard();
}

void writeDataToCard()
{
  if ( !mfrc522.PICC_IsNewCardPresent()) {
    return;
  }
  if ( !mfrc522.PICC_ReadCardSerial()) {
    return;
  }
  Serial.println("card selected");

  strcat((char*)codeBuffer, item1.barcode);

  writeBlock(2, codeBuffer); //this is not how it should be. this will only write the barcode of item1.

}


void recvWithEndMarker() {
  static byte ndx = 0;
  char endMarker = '\n';
  char rc;

  while (Serial.available() > 0 && newData == false) {
    rc = Serial.read();

    if (rc != endMarker) {
      receivedChars[ndx] = rc;
      ndx++;
      if (ndx >= numChars) {
        ndx = numChars - 1;
      }
    }
    else {
      receivedChars[ndx] = '\0'; // terminate the string
      ndx = 0;
      newData = true;
    }
  }
}

void processData() {
  if (newData == true) {
    Serial.print("This just in ... ");
    Serial.println(receivedChars);

    if (strcmp(receivedChars, item1.barcode) == 0)
    {
      total = total + item1.price;
      Serial.print(item1.name);
    }

    else if (strcmp(receivedChars, item2.barcode) == 0)
    {
      total = total + item2.price;
      Serial.print(item2.name);
    }
    newData = false;
  }
}


int writeBlock(int blockNumber, byte arrayAddress[]) 
{
  int largestModulo4Number=blockNumber/4*4;
  int trailerBlock=largestModulo4Number+3;
  if (blockNumber > 2 && (blockNumber+1)%4 == 0){
    Serial.print(blockNumber);
    Serial.println(" is a trailer block:");
    return 2;
  }

  byte status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(mfrc522.uid));

  if (status != MFRC522::STATUS_OK) {
    return 3;//
  }

  status = mfrc522.MIFARE_Write(blockNumber, arrayAddress, 16);
  if (status != MFRC522::STATUS_OK) {
    return 4;
  }
}

Of course one method is to put the writeDataToCard() routine inside each if-else but that is not efficient. What I want to do is that the user only scan the card after he finishes shopping.

P/S: Pardon me if the title is not suitable. I couldn't find a better description for the subject.

Also thanks to @Robin2 for his Serial Input Basics - updated

struct ItemList implies that it's a list; it;s not, it's an item :wink:

I would change that to

struct ITEM
{
  char barcode[10];
  char name[25];
  unsigned int price;
};

And next use an array of items

ITEM ItemList[] =
{
  {
    "452795615",
    "SOME ITEM_1  $1.00",
    100
  },
  {
    "562330250",
    "SOME ITEM_2  $1.80",
    180
  },
};

Now in processData, you can simply loop through the array; imagine you had 100 or 1000 barcodes, that would imply 100 or 1000 strcmp statements.

Next you have a code buffer declared as a byte array; why do you cast it to a char* ? You could just as well declare it as a char buffer.

And what is probably a bug

  strcat((char*)codeBuffer, item1.barcode);

The use of strcat makes that you will write outside the boundaries after the first strcat. You either need to clear codeBuffer

codeBuffer[0]='\0';

or use strcpy instead of strcat.

If you want to store the scanned items, I would use an additional array to store the index of the scanned items of the above ItemList array. User scans the second barcode "562330250", store the index (1) in the array and increment the position where to write the next index. Initialize the array with invalid values (e.g. -1) so you know where to stop at check out. You can them loop through additional array, find the selected items and write them to the card.

struct ITEM Details
{
  char barcode[10];
  char name[25];
  unsigned int price;
};

Or better, ITEM Details. But gosh, please don't bother such trivial matter .

strcat((char*)codeBuffer, item1.barcode);

Because in the writeBlock function, it requires byte array but my barcode needs to be in char array.

ITEM ItemList[] =
{
  {
    "452795615",
    "SOME ITEM_1  $1.00",
    100
  },
  {
    "562330250",
    "SOME ITEM_2  $1.80",
    180
  },
};

I knew accounting for all the possibilities using if-else is a noob method but it is because I dont know how to implement a loop on that array is why I didnt make one. Did you mean I need to use for loop through the array? Perhaps more clue?

If you want to store the scanned items, I would use an additional array to store the index of the scanned items of the above ItemList array. User scans the second barcode "562330250", store the index (1) in the array and increment the position where to write the next index. Initialize the array with invalid values (e.g. -1) so you know where to stop at check out. You can them loop through additional array, find the selected items and write them to the card.

I am trying to get this into my head too.

So I did this

  for(int i=0; i<10; i++){
      
      if (strcmp(receivedChars, ItemList[i].barcode) == 0)
      {
      total = total + ItemList[i].price;
      Serial.print(ItemList[i].name);
      }
    }

Yepp great suggestion. I got the expected result. But am I doing it correctly?

Looks good. If this is only a checkout point, that will work to show the item and sim the total. You can add the write to card in there as well.

Ignore my comment about the second array, that was meant for when an item was added to a basket.

Well I got some workaround, by adding a bool in Item struct.

struct Item {
  char barcode[14];
  char name[25];
  unsigned int price;
  byte count;
  bool added;
};

When the user scans an item, the bool changes to true. And when the user scans the card, it goes through for loop of the array and check if the bool of an item is true. If it is, it writes on the card.

for(int i=0; i<3; i++)
{
     if(ItemList[i].added == true)
     {
      strcat((char*)codeBuffer, ItemList[i].barcode);
      writeBlock(block, codeBuffer);
      codeBuffer[0] = '\0';
      block++;
     }
}

This does what I want ultimately. But is this method preferable? Is it good in the long run? I would like to hear what the experts think.

So assuming that the above method is fine enough, I would like to take one step further. So far I have only been writing the barcode number on the block of the card. Now, I also want to add the count for the item. So modifying the struct again I have this

struct Item {
  char barcode[14];
  char name[25];
  unsigned int price;
  unsigned int count; //or should it be char? byte? no one buys the same item 255 times.
  bool added;
};

Basically when the user scans an item, the count increases by 1. This keep track of the item count. So far so good. Now, I want to write the item count on the same block of the same item barcode.

34 35 32 37    39 35 36 31    35 00 00 00    00 00 00 00

After writing the barcode number on the card, I have 7 more bytes available which I plan to write the count number on it. The problem is the library I use write the data by block, not by the specific byte. So I definitely cannot write on the byte I want only(for example in this case, byte 9). If I was to write that function in the library, well that's not yet my level. So whats your suggestion folks?

I am using Mifare Classic 1K tag. I can write the count on another block but thats a waste.

You can leave the boolean out; count can take that function (a count of 0 means not added).

But I feel I need an understanding the basics of the process that you see. Is this kind of a shopping cart where the user puts items in a cart; before adding it to the cart, the user scans it? And at checkout, some (or all) of the info is written to the Mifare card?

Can you give a full description of the process, please.

sterretje:
You can leave the boolean out; count can take that function (a count of 0 means not added).

But I feel I need an understanding the basics of the process that you see. Is this kind of a shopping cart where the user puts items in a cart; before adding it to the cart, the user scans it? And at checkout, some (or all) of the info is written to the Mifare card?

Yes that is how it should be. The count is to track how many number of the same items has the user scan. Meaning that if the user scan the same item two times, the count = 2. The info needed to be written is only the barcode number and the how much item of the same type has been scanned a.k.a count.

So can you see it now? The count is not the boolean of wheter the item has been added or not. But to keep track of how many an item of the same type for example, a soap has been scanned. Suppose that the user wants to buy 2 soap, he should scan it two times making the count = 2.

Can you give a full description of the process, please.

I forgot to read this sentence.

  1. Device boots up.
  2. User scan any item he wants to buy. If he scans the same item more than once, it means he wants to buy it more than one.
  3. After he finishes scanning all the item he wants to buy, he put the card on the MFRC522, and the device will write the information(barcode and count) of the item he scanned on the card.
  4. Shopping done.

There will be another device(using Arduino UNO) which will read the information inside the card. This information will be passed to a VB app. Or you could call this is the cashier side that the user will pass his card to.

I need to know the barcode because thats the only way the VB app will differentiate each item. And I also need to know the count because who buy only one packet of sugar everytime he goes for grocery shopping?. So the VB app also needs know the count of the item. I dont need anything else to be written on the card such as price and name because VB can handle that one.

You don't need an extra block on the Mifare.

With the current setup that you have (an array / list of items in RAM), you can update the count of each item.
At checkout (when the card is presented), you can write barcode and count to the Mifare. I have no experience with the Mifare but you seem to be well on the way. The only thing that is missing is that you have to reset all item counts to 0 after the checkout is complete, else the next time one shops there is still the old data.

I don't know how big a block is in the Mifare; you can collect data first and do a single write. E.g. if a block is 32 bytes, a barcode is 14 bytes (excluding nul terminator) and you have a one byte count, you can store two items with count in a 32 byte array and write that to the Mifara block.

Another point is that your items are currently hardcoded. What if there is a change in inventory (something removed, something added)? Currently you have to update your program. I would store the items in eeprom and have a separate count (going back to my suggestion of the second array). That second array will now be a struct again that simply keeps the index of the selected item and the count.

At checkout, you read the barcode (from eeprom) that each index refers to and store it on the card together with the count.

You can implement a special mode that allows you to update the inventory from e.g. the VB app.

sterretje:
You don't need an extra block on the Mifare.

With the current setup that you have (an array / list of items in RAM), you can update the count of each item.
At checkout (when the card is presented), you can write barcode and count to the Mifare. I have no experience with the Mifare but you seem to be well on the way. The only thing that is missing is that you have to reset all item counts to 0 after the checkout is complete, else the next time one shops there is still the old data.

In order to write the barcode and the count in the same block(and I must write it in the same block) I think I need to combine the byte array of barcode with the integer count? I dont know how to do this. I need some clue on how can I convert that count into byte array(or byte?) and combine it(memcpy? strcat?) with the barcode and place it in some buffer. Then a single write will place the buffer containing the information into the card.

I don't know how big a block is in the Mifare; you can collect data first and do a single write. E.g. if a block is 32 bytes, a barcode is 14 bytes (excluding nul terminator) and you have a one byte count, you can store two items with count in a 32 byte array and write that to the Mifara block.

A block consist of 16 bytes. My barcode already fills in 9 bytes. So, I have 7 bytes left which I can write the count onto it. So 1 block = 1 barcode and count for an item.

Another point is that your items are currently hardcoded. What if there is a change in inventory (something removed, something added)? Currently you have to update your program. I would store the items in eeprom and have a separate count (going back to my suggestion of the second array). That second array will now be a struct again that simply keeps the index of the selected item and the count.

At checkout, you read the barcode (from eeprom) that each index refers to and store it on the card together with the count.

You can implement a special mode that allows you to update the inventory from e.g. the VB app.

Yes I have foreseen this weakness but I would like to settle the above problem first.

I think that the problem I always encounter is that the fact the library requires byte array in order for data to be written on the block.

Your suggestion is great but it's still hard for me to imagine the code.

You can define a new struct that will only be used when the data needs to be written to card. It will contain the barcode and the count.

// a struct to hold data to be written to card
struct ITEM2WRITE
{
  char barcode[BARCODESIZE];
  unsigned int count;
};

BARCODESIZE is defined as 14 allowing for a 13 character barcode plus a nul terminator.

The top of your code now looks like

// max size of barcode (including nul terminator)
#define BARCODESIZE 14

// a struct to hold data to be written to card
struct ITEM2WRITE
{
  char barcode[BARCODESIZE];
  unsigned int count;
};

struct ITEM
{
  char barcode[BARCODESIZE];
  char name[25];
  unsigned int price;
  unsigned int count; //or should it be char? byte? no one buys the same item 255 times.
};

ITEM itemList[] =
{
  {
    "452795615",
    "SOME ITEM_1  $1.00",
    100,
    0
  },
  {
    "562330250",
    "SOME ITEM_2  $1.80",
    180,
    0
  },
};

The below demonstrates how to use it

void setup()
{
  // put some stuff in the basket
  itemList[0].count = 2;

  // checkout; write items to card
  ITEM2WRITE itm;
  for (int cnt = 0; cnt < sizeof(itemList) / sizeof(itemList[0]); cnt++)
  {
    // if item was scanned one or more times
    if (itemList[cnt].count != 0)
    {
      // copy barcode and count from list
      strcpy(itm.barcode, itemList[cnt].barcode);
      itm.count = itemList[cnt].count;

      // write to successive Mifare blocks starting at 2
      writeBlock(cnt + 2, (byte*)&itm);

      // remove from basket
      itemList[cnt].count = 0;
    }
  }
}

void loop()
{
}

int writeBlock(int blockNumber, byte *blockData)
{

}

Note that I have changed writeBlock slightly; the name of the second variable now reflects what is actually written.

I'm getting back to my original comment on your cast where you used the casts incorrectly. In the above code, I use

      // write to successive Mifare blocks starting at 2
      writeBlock(cnt + 2, (byte*)&itm);

This casts the address of itm to a byte pointer that is expected by writeBlock.

I hope that this gets you on track.

Notes / questions
1)
for further help, you need to post an updated code
2)
how many items max in itemList?
3)
which Arduino do you intend to use for this? The amount of available RAM will determine how to store selected items (the second array tat I mentioned) when you store itemList in eeprom.

Wow, I didn't know you could manipulate the language to that way. Got more to learn huh.

Anyway, thanks for spending your effort to help. I really appreciate it.

Okay I have tried your code and modified it to suit my program.

This is my latest code

#include <SPI.h>
#include <MFRC522.h>

#define SS_PIN 7  
#define RST_PIN 6

#define BARCODESIZE  10
MFRC522 mfrc522(SS_PIN, RST_PIN);       
MFRC522::MIFARE_Key key;




struct ITEM2WRITE
{
  char barcode[BARCODESIZE];
  unsigned int count;
};

struct Item
{
  char barcode[BARCODESIZE];
  char name[25];
  unsigned int price;
  unsigned int count; //or should it be char? byte? no one buys the same item 255 times.
};

Item ItemList[] =
{
  {
    "452795615",
    "SOME ITEM_1  $1.00",
    100,
    0
  }
  ,
  {
    "562330250",
    "SOME ITEM_2  $1.80",
    180,
    0
  }
  ,
};

boolean newData = false;
const byte numChars = 12;
char receivedChars[numChars];


void setup()
{
  Serial.begin(9600);      
  SPI.begin();               
  mfrc522.PCD_Init();   

  for (byte i = 0; i < 6; i++) {
    key.keyByte[i] = 0xFF;
  }
}

void loop()
{
  recvWithEndMarker();
  processData();
  writeData();
}

void writeData()
{
  ITEM2WRITE itm;

  if ( !mfrc522.PICC_IsNewCardPresent()) {
    return;
  }
  if ( !mfrc522.PICC_ReadCardSerial()) {
    return;
  }
  Serial.println("card selected");

  for (int cnt = 0; cnt < sizeof(ItemList) / sizeof(ItemList[0]); cnt++)
  {
    // if item was scanned one or more times
    if (ItemList[cnt].count != 0)
    {
      // copy barcode and count from list
      strcpy(itm.barcode, ItemList[cnt].barcode);
      itm.count = ItemList[cnt].count;

      // write to successive Mifare blocks starting at 2
      writeBlock(cnt + 2, (byte*)&itm);
    }
  }
}

int writeBlock(int blockNumber, byte *arrayAddress) 
{
  int largestModulo4Number=blockNumber/4*4;
  int trailerBlock=largestModulo4Number+3;
  if (blockNumber > 2 && (blockNumber+1)%4 == 0){
    Serial.print(blockNumber);
    Serial.println(" is a trailer block:");
    return 2;
  }

  byte status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(mfrc522.uid));

  if (status != MFRC522::STATUS_OK) {
    return 3;//
  }

  status = mfrc522.MIFARE_Write(blockNumber, arrayAddress, 16);
  if (status != MFRC522::STATUS_OK) {
    return 4;
  }
}

void recvWithEndMarker() {
  static byte ndx = 0;
  char endMarker = '>';
  char rc;

  while (Serial.available() > 0 && newData == false) {
    rc = Serial.read();

    if (rc != endMarker) {
      receivedChars[ndx] = rc;
      ndx++;
      if (ndx >= numChars) {
        ndx = numChars - 1;
      }
    }
    else {
      receivedChars[ndx] = '\0'; // terminate the string
      ndx = 0;
      newData = true;
    }
  }
}

void processData() {
  if (newData == true) {
    Serial.print("This just in ... ");
    Serial.println(receivedChars);

    for(int i=0; i<3; i++){

      if (strcmp(receivedChars, ItemList[i].barcode) == 0)
      {
        ItemList[i].count++;
        Serial.println(ItemList[i].name);
      }
    }
    newData = false;
  }
}

Written successful. So In order to check the content of the card to verify that I am writing the correct information here's another code to read the card.

#include <SPI.h>
#include <MFRC522.h>

#define SS_PIN 7  
#define RST_PIN 6  
MFRC522 mfrc522(SS_PIN, RST_PIN);
MFRC522::MIFARE_Key key;


void setup() {
  Serial.begin(9600);        
  SPI.begin();               
  mfrc522.PCD_Init();       
  Serial.println("Scan a MIFARE Classic card");

  for (byte i = 0; i < 6; i++) {
    key.keyByte[i] = 0xFF;
  }

}

int block=2;

byte readbackblock[10];

void loop()
{

  if ( ! mfrc522.PICC_IsNewCardPresent()) {
    return;
  }

  if ( ! mfrc522.PICC_ReadCardSerial()) {
    return;
  }

  Serial.println("card selected");

  readBlock(block, readbackblock);
  Serial.print("read block: ");
  for (int j=0 ; j<11 ; j++)
  {
    Serial.print(readbackblock[j]);
  }
}



int readBlock(int blockNumber, byte arrayAddress[]) 
{
  int largestModulo4Number=blockNumber/4*4;
  int trailerBlock=largestModulo4Number+3;

  byte status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(mfrc522.uid));

  if (status != MFRC522::STATUS_OK) {
    Serial.print("PCD_Authenticate() failed (read): ");
    Serial.println(mfrc522.GetStatusCodeName(status));
    return 3;
  }

  byte buffersize = 18;
  status = mfrc522.MIFARE_Read(blockNumber, arrayAddress, &buffersize);
  if (status != MFRC522::STATUS_OK) {
    Serial.print("MIFARE_read() failed: ");
    Serial.println(mfrc522.GetStatusCodeName(status));
    return 4;
  }
  Serial.println("block was read");
}

And this is the result

52535055575354495303

525350555753544953 <- barcode number in hex

03 <- count number but what format is this?

So as you can see, I am not getting the correct count value or maybe I am missing some steps.

And what is the number of bytes of the count? In the above code I was assuming it is 2 bytes hence the for loop goes through 11 times. My barcode is only 9 bytes.

Notes / questions
1)
for further help, you need to post an updated code
2)
how many items max in itemList?
3)
which Arduino do you intend to use for this? The amount of available RAM will determine how to store selected items (the second array tat I mentioned) when you store itemList in eeprom.

  1. Posted

  2. Not yet confirmed since this is only a prototype. Not yet an official product/project.

  3. Arduino UNO.

The zero before the three is the nul terminating character that C adds to the barcode character array in the ITEM struct.

Also be aware that Serial.println omits the leading zeros. So the zero that you see is actually 00 and the 3 that you see is actually 03 (both decimal). You can change the count to e.g. 11 and see what happens with the 3.

Also be aware that your result is NOT in hexadecimal but decimal

E.g. 52d (the first character of your barcode) equals 34h which is the character '4'. See e.g. http://asciitable.com/

About your second code (the read back), why is buffersize defined as 18 while readbackblock is only 10 bytes? You will overwrite memory that does not belong to readbackblock and crashes can be the result.

Your code is fine for verifying (if it's bug free :wink: )

To read an item back from the card, you can use the same approach as with the write using a cast.

#define BARCODESIZE  10

struct ITEM2WRITE
{
  char barcode[BARCODESIZE];
  unsigned int count;
};

void setup()
{
  Serial.begin(250000);
  Serial.println("Ready");
  
  ITEM2WRITE itm;

  // read a block from Mifare
  readBlock(2, (byte*)&itm);
  
  // display item
  Serial.println(itm.barcode);
  Serial.println(itm.count);
}

void loop()
{
  // put your main code here, to run repeatedly:

}

/*
  simulation of mifare read for demo purposes
*/
int readBlock(int blockNumber, byte arrayAddress[])
{
  // data struct with barcode
  byte data[sizeof(ITEM2WRITE)] = "123456789";
  // add count; read up on little endian / big endian why it is stored in this sequence
  data[10] = 3;
  data[11] = 0;

  // simulate a read of the block into array address
  memcpy(arrayAddress, data, sizeof(ITEM2WRITE));

  // debug print (in HEX)
  for (int cnt = 0; cnt < sizeof(ITEM2WRITE); cnt++)
  {
    Serial.println(arrayAddress[cnt], HEX);
  }
}

Just amazing. Thank you so much.

I got the expected result, along with the count number. Now I just need to add one more function, REMOVE ITEM MODE where I think I will be able to do it.

Also be aware that your result is NOT in hexadecimal but decimal

Forgive me for my incompetence. I really have a LOT to learn. Seeing how just use those cstring functions to get the code working is really great.

Actually, I had found a solution yesterday by converting the integer of count to byte array...then writing it to the card because the readBlock function needs byte array. I didn't know you could create the same struct and cast the byte* that way. Your method is simple and preferable.

I don't know what is encouraging you to help me to this point, but all I could say is thank you.

I will hit this thread again when I encounter any other problem.

cyberjupiter:
I don't know what is encouraging you to help me to this point, but all I could say is thank you.

Sharing is caring :wink: And it's fun to solve problems 8)