Using Barcodes on the MES screens

Hi all,

I have an issue trying to use barcodes (code 39) on the Epicor MES screens.

The barcode is set up as Job, Asm and Oper so the barcode is 338832010 (they do have a * at the start and after) we use Zebra LS2208 scanners and these have been programmed using ADF to :-

Set Code 39
Send the next 6 characters
Send TAB
Send the next character
Send TAB
Send the next 2 characters
Send Tab

If I scan the barcode to say notepad everything looks fine but when I try to do Start Setup Activity it populates the Job number tabs to Assembly then just sits there ?

The weird thing is if I go to Time & Expense entry select new time detail click in the Job number field and scan the same code with the same scanner it works fine.

Is there something different with how the MES screens operate?

Thanks,
John

Put about a 5 second pause after the Job Number as it validates and retrieves it before tabing to the Assembly.

Set Code 39
Send the next 6 characters
Send TAB
Send the next character
Send TAB
PAUSE 5 SECONDS
Send the next 2 characters
Send Tab

4 Likes

Hi,

Thanks very much that solved the issue.

Best Regards,
John

Hello cchang! Do you happen to know the character to PAUSE the Dataworks barcode

We used the 123Scan utility to program the pause code into the scanner with the Advance Data Formatting rules. This utility tool allows you to set the pause after scanning the job number.

2 Likes

Can I put a field on MES which will display a barcode (job number field)

1 Like

You can however you will need a barcode reader capable of reading off a screen. Most wonā€™t.

You can get image sensor based ones that will though.

AFAIK it has to do with pixel ratio for 2D codes. I have a $33 2D works fine reading screen codes off PDF renderings. (Scanner)

Read right off screen:

1 Like

Iā€™ll have to dig out the code, but I did something similar a few years back at a previous employer which bypassed the issue that you are having with the data being entered into the wrong fields.

Basically, what I did was this: On the main MES Screen I set up a key-binding looking for a certain control character (CTRL-J I think I used). When MES detected that key combo, it would listed for another key-binding (CTRL-K, if memory serves).

I had barcodes set up to send [^J][JobNum]|[AsmSeq]|[OpSeq]^K, which would look like this: ^J123456|0|50^K.

The user would log into MES and scan the barcode on the traveler. MES would see the ^J, and then parse the rest of the data into their appropriate variables (JobNum, AsmSeq, OpSeq). It would then spawn the Start Production Activity process passing the JobNum, AsmSeq, and OpSeq to that process.

That shop was fairly basic with the Start Production Activity screen and did not require any other information to be entered outside of those 3 pieces that I mentioned, so I went another stop further and had the main MES screen trigger the ā€œSubmitā€ process on the Start Production Activity screen as well. This meant the only thing that the end user had to do was make sure they were logged into MES and scan the barcode. Within a few seconds they would see that job/asm/op appear in the list of active labor transactions without having to do anything else. It worked great. I had PDF417 barcodes added to the Job Travelers for each Op Seq, making the process basically ā€˜end user proofā€™ :slight_smile:

I will dig through my files and see if I canā€™t find that customization somewhere. It has been 7 years and 3 jobs ago, so it may take me a minute or 30 to find it.

4 Likes

This sounds very cool. Iā€™d be thrilled if you are able to share any files.

Iā€™d love to see that code too, if you can share.

1 Like

Iā€™m digging around for it. I have a dozen hard drives and countless thumb drives I am looking through when I have a few minutes of downtime. (still getting settled into the new job)

2 Likes

After some digging around, I was able to find the code that I wrote for the main MES screen to handle reading from the barcode scanner.

So this is an example barcode that was put onto our Job Travelerā€™s at the start of every Operation in the Traveler:
image

To save yā€™all the hassle of grabbing your barcode scanner to try and scan a blurry barcode, this is the data stored in the Barcode:
$|627460|90|4|04052019|#

This code is in the MES Customization: (This was one of the 1st C# projects that I did. I have learned a LOT since this was done, and would do things a bit differently now.)

public class Script
{
	// Begin Wizard Added Module Level Variables **

	private DateTime _lastKeystroke;
	private List<char> _barcode;
	private LaunchFormOptions lfo;
	private bool readingBarcode;

	// Initializes the _lastKeystroke field to January 1, 0001. This ensures that it will be updated to the current time when the first keystroke occurs.
	_lastKeystroke = new DateTime(0L);

	// This initializes the _barcode field as a List<char> with an initial capacity of 250 characters.
	_barcode = new List<char>(250);

	// This creates a new instance of the LaunchFormOptions class.
	lfo = new LaunchFormOptions();

	// This initializes the readingBarcode field to false, indicating that the program is not currently processing a barcode
	readingBarcode = false;
	
	// End Wizard Added Module Level Variables **

	public void MESMenu_KeyPress(object sender, KeyPressEventArgs e)
	{
		// Create a TimeSpan object to measure the time difference between the current time (DateTime.Now) and the time when the last keystroke was registered.
		TimeSpan timeSpan = DateTime.Now - _lastKeystroke;

		// Set _lastKeyStroke to the current DateTime
		_lastKeystroke = DateTime.Now;

		// Check whether the time difference between the current keystroke and the previous one is greater than 150 milliseconds. If it takes longer than 150 milliseconds between key presses, the system considers it a new barcode input or new input sequence.
		if (timeSpan.TotalMilliseconds > 150.0)
		{
			// Clear out the _barcode as this is most likely the start of a new input sequence.
			_barcode.Clear();
		}

		// Add the current key (e.KeyChar) to the _barcode list
		_barcode.Add(e.KeyChar);

		// This condition checks if the pressed key is "#" and if the _barcode list contains at least one character. 
		// The # symbol indicates the end of the barcode sequence.
		if (Operators.CompareString(Conversions.ToString(e.KeyChar), "#", false) == 0 && _barcode.Count > 0)
		{
			// Essentially, this takes the characters collected in the _barcode list (which were originally individual characters) and reconstructing them into a single string.
			// Could I have done this differently and just constructed the string when the e.KeyChar was captured? Yes. Why didn't I? I have no idea. It was nearly 7 years ago.
			string text = new string(_barcode.ToArray());

			// Set the menu to launch as a Modal dialog (Always On Top)
			lfo.IsModal = true;

			// Set lfo.ContextValue to the 'text' variable so it can be accessed from the new menu that will open.
			lfo.ContextValue = text;

			// Launch the UDMES2 menu, passing the LaunchFormOptiosn to it (lfo)
			// The window will be 'top most' so it does not accidentally get hidden.
			// The main MES window is locked until UDMES2 is closed
			ProcessCaller.LaunchForm(oTrans, "UDMES2", lfo);

			// Clear the _barcode as we are done with it now.
			_barcode.Clear();
			
			// Refresh the LaborData so that the listbox in the MES window shows the new operation
			oTrans.RefreshLaborData();
		}
	}
}

I am still looking for the code that was used in the UDMES2 screen, but it is basically something like this:

  1. Check to see if LaunchFormOptions.ContextValue.ToString() != null
  2. Take LaunchFormOptions.ContextValue.ToString() and break up the values into an array or list, indicating | as the separator between the different elements.
  3. Place the appropriate element of the list that was just created into the appropriate fields in the form (I donā€™t recall the field names off hand). Basically, the JobNum goes into the JobNum field, OpSeq into the OpSeq field, etc.
  4. Invoke the .Click() event on the Submit button.
  5. Watch the form close.

While the code is obviously not very optimized or well thought out, it worked, and it worked flawlessly for the entire time I was with that company (6 years). Would I do it differently if I had to do it now? Most definitely. Looking at the code now, I just realized that I never did anything to check for the start of a barcode by looking for the $ symbol (which is what the barcode starts with).

I have learned a lot from the community here over the years and know that there are much better ways that I could have done this to accomplish the same thing. But hey, we all have to start somewhere, right?

4 Likes

This is fantastic! Thanks for sharing!!

And for any of those interested, here is the code for the DLL that I had loaded into SSRS to generate the PDF417 barcodes on-demand. When the Job Traveler was printed from Epicor, the RDL would load the namespace of the DLL from SSRS which would make the functions from that class available to SSRS.

image

NOTE: Keep in mind that the Namespaces, class names, method names, etc., have changed as I was developing this, and learning new things as I went. So what is shown here may not always match with other code snippets shown here. However, it should be clear enough what is going on so that anyone wanting to try something similar can use this as a reference, and not treat it as gospel. When/if I ever find the final stuff, I will update these posts accordingly.

Anyways, on with the codeā€¦In the report itself, I created an Image field, and added an expression to it, like this:

=SSRS_Barcodes.SSRS_Barcode_Generator.Generate_Barcode("PDF417", "$" + Fields!JobNum.Value + "|" + Fields!OprSeq.Value + "|" + Fields!RunQty.Value + "|" + Fields!JobOper_DueDate.Value + "#")

That turns this box in the Report Builder
image
into this:
image

The code below is for the DLL that gets loaded into SSRS and basically returns a BMP images as an array of bytes that gets displayed in the final report output.

Expand for full code
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using SSRS_Barcodes.My.Resources;
using ZXing;
using ZXing.Common;
using ZXing.QrCode.Internal;
using ZXing.Rendering;

namespace SSRS_Barcodes
{	
	public class SSRS_Barcode_Generator
	{
		public enum BarcodeType
		{
			AZTEC = 0,
			DATA_MATRIX = 1,
			QR_CODE = 2,
			PDF417 = 3
		}
		public static byte[] Generate_Barcode(int bcType, string str, int x = 0, int y = 0, int m = 0)
		{
			byte[] bc = null;
			if (bcType == 0)
			{
				bc = SSRS_Barcode_Generator.Generate_Barcode_AZTEC(str);
			}
			else if (bcType == 1)
			{
				bc = SSRS_Barcode_Generator.Generate_Barcode_DATA_MATRIX(str);
			}
			else if (bcType == 2)
			{
				bc = SSRS_Barcode_Generator.Generate_Barcode_QR_CODE(str, x, y, m);
			}
			else if (bcType == 3)
			{
				bc = SSRS_Barcode_Generator.Generate_Barcode_PDF417(str);
			}
			return bc;
		}
		public static byte[] Generate_Barcode_PDF417(string str)
		{
			Image image = new BarcodeWriter
			{
				Format = BarcodeFormat.PDF_417
			}.Write(str);
			MemoryStream ms = new MemoryStream();
			image.Save(ms, ImageFormat.Bmp);
			return ms.GetBuffer();
		}
		public static byte[] Generate_Barcode_AZTEC(string str)
		{
			Image image = new BarcodeWriter
			{
				Format = BarcodeFormat.AZTEC
			}.Write(str);
			MemoryStream ms = new MemoryStream();
			image.Save(ms, ImageFormat.Bmp);
			return ms.GetBuffer();
		}
		public static byte[] Generate_Barcode_DATA_MATRIX(string str)
		{
			Image image = new BarcodeWriter
			{
				Format = BarcodeFormat.DATA_MATRIX
			}.Write(str);
			MemoryStream ms = new MemoryStream();
			image.Save(ms, ImageFormat.Bmp);
			return ms.GetBuffer();
		}
		public static byte[] Generate_Barcode_QR_CODE(string str, int x, int y, int m)
		{
			BarcodeWriter barcodeWriter = new BarcodeWriter();
			EncodingOptions encOptions = new EncodingOptions
			{
				PureBarcode = false,
				Width = x,
				Height = y,
				Margin = m
			};
			encOptions.Hints.Add(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
			barcodeWriter.Renderer = new BitmapRenderer();
			barcodeWriter.Options = encOptions;
			barcodeWriter.Format = BarcodeFormat.QR_CODE;
			Bitmap bitmap = barcodeWriter.Write(str);
			Bitmap overlay = new Bitmap(Resources.BlackFlag);
			checked
			{
				int deltaHeight = bitmap.Height - overlay.Height;
				int deltaWidth = bitmap.Width - overlay.Width;
				Graphics.FromImage(bitmap).DrawImage(overlay, new Point((int)Math.Round((double)deltaWidth / 2.0), (int)Math.Round((double)deltaHeight / 2.0)));
				MemoryStream ms = new MemoryStream();
				bitmap.Save(ms, ImageFormat.Bmp);
				return ms.GetBuffer();
			}
		}
	}
}

You can also generate other barcode types such as QR Codes or AZTEC codes using that library if the situation calls for it. You just need to change the data you are passing to the methods in the SSRS Imagebox (some require the width and height, .

That is everything I am able to find right now. Hopefully it is enough to help point someone in the right direction with how to start an endeavor such as this.

1 Like