0

I am using this application to get a weight from a scale to be read into another application. long story short once the file exists, the other application reads it then deletes the file. If I try and run this multiple times in a row, it does not get the weight. Not like it errors out or throws an exception I just have to wait about 5 Minutes. My suspect is that it is a writer or possible object handle that isn't being let go when the program exits. I presume the JVM has to actually close before I run it for it to run properly. Can someone point me in the right Direction? github.com/cshort2112/dymo_s100_interface

package Model;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class Init {

    //Vendor ID = 0x0922
    //Product ID = 0x8009

    public static void main(String[] args) {
        try {
            UsbScale scale = UsbScale.findScale();
            assert scale != null;
            scale.open();
            double weight = 0;
            try {
                while (weight == 0) {
                    weight = scale.syncSubmit();
                }
            } finally {
                scale.close();
            }
            weight = (double)Math.round(weight * 10d) / 10d;
            try {
                String home = System.getProperty("user.home");
                File f = new File(home + File.separator + "Documents" + File.separator + "Weight.txt");

                BufferedWriter out = new BufferedWriter(new FileWriter(f));
                try {
                    out.write(String.valueOf(weight));
                } finally {
                    out.close();
                }
            } catch (IOException e) {
                System.out.println("An error occurred.");
                e.printStackTrace();
            }
        } catch (Exception e) {
            try {
                String home = System.getProperty("user.home");
                File f = new File(home + File.separator + "Documents" + File.separator + "Weight.txt");

                BufferedWriter out = new BufferedWriter(new FileWriter(f));
                try {
                    out.write("Error! " + e.getMessage());
                } finally {
                    out.close();
                }
            } catch (IOException exception) {
                System.out.println("An error occurred.");
                exception.printStackTrace();
            }
        }

    }

}

and the usbscale.java:

package Model;

import org.usb4java.*;

import javax.usb.*;
import javax.usb.event.UsbPipeDataEvent;
import javax.usb.event.UsbPipeErrorEvent;
import javax.usb.event.UsbPipeListener;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

public class UsbScale implements UsbPipeListener {

    private final UsbDevice device;
    private UsbInterface iface;
    private UsbPipe pipe;
    private final byte[] data = new byte[6];
    private double finalWeight;
    private Context context;

    private UsbScale(UsbDevice device) {
        this.device = device;
    }


    public static UsbScale findScale() {
        try {
            UsbServices services = UsbHostManager.getUsbServices();
            UsbHub rootHub = services.getRootUsbHub();
            // Dymo S100 Scale:
            UsbDevice device = findDevice(rootHub, (short) 0x0922, (short) 0x8009);
 //           // Dymo M25 Scale:
 //           if (device == null) {
 //               device = findDevice(rootHub, (short) 0x0922, (short) 0x8004);
 //           }
            if (device == null) {
                return null;
            }
            return new UsbScale(device);
        } catch (Exception e) {
            try {
                String home = System.getProperty("user.home");
                File f = new File(home + File.separator + "Documents" + File.separator + "Weight.txt");

                BufferedWriter out = new BufferedWriter(new FileWriter(f));
                try {
                    out.write("Error! " + e.getMessage());
                } finally {
                    out.close();
                }
            } catch (IOException exception) {
                System.out.println("An error occurred.");
                exception.printStackTrace();
            }
            return null;
        }
    }

    private static UsbDevice findDevice(UsbHub hub, short vendorId, short productId) {
        for (UsbDevice device : (List<UsbDevice>) hub.getAttachedUsbDevices()) {
            UsbDeviceDescriptor desc = device.getUsbDeviceDescriptor();
            if (desc.idVendor() == vendorId && desc.idProduct() == productId) {
                return device;
            }
            if (device.isUsbHub()) {
                device = findDevice((UsbHub) device, vendorId, productId);
                if (device != null) {
                    return device;
                }
            }
        }
        return null;
    }

    public Device findDevice(short vendorId, short productId)
    {
        int result = LibUsb.init(context);
        if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to initialize libusb.", result);
        // Read the USB device list
        DeviceList list = new DeviceList();
        result = LibUsb.getDeviceList(context, list);
        if (result < 0) throw new LibUsbException("Unable to get device list", result);
        try
        {
            // Iterate over all devices and scan for the right one
            for (Device device: list)
            {
                DeviceDescriptor descriptor = new DeviceDescriptor();
                result = LibUsb.getDeviceDescriptor(device, descriptor);
                if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to read device descriptor", result);
                if (descriptor.idVendor() == vendorId && descriptor.idProduct() == productId) {
                    return device;
                }
            }
        }
        finally
        {
            // Ensure the allocated device list is freed
            LibUsb.freeDeviceList(list, true);
        }

        // Device not found
        return null;
    }

    public void open()  {
        try {
            context = new Context();
            UsbConfiguration configuration = device.getActiveUsbConfiguration();
            iface = configuration.getUsbInterface((byte) 0);
            // this allows us to steal the lock from the kernel
            DeviceHandle handle = new DeviceHandle();
            int result = LibUsb.open( findDevice((short) 0x0922, (short) 0x8009), handle);
            if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to open USB device", result);
            result = LibUsb.setConfiguration(handle, 0);
            if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to set Configuration", result);
            iface.claim(usbInterface -> true);
            final List<UsbEndpoint> endpoints = iface.getUsbEndpoints();
            pipe = endpoints.get(0).getUsbPipe(); // there is only 1 endpoint
            pipe.addUsbPipeListener(this);
            pipe.open();
        } catch (Exception e) {
            try {
                String home = System.getProperty("user.home");
                File f = new File(home + File.separator + "Documents" + File.separator + "Weight.txt");

                BufferedWriter out = new BufferedWriter(new FileWriter(f));
                try {
                    out.write("Error! " + e.getMessage());
                } finally {
                    out.close();
                }
            } catch (IOException exception) {
                System.out.println("An error occurred.");
                exception.printStackTrace();
            }
        }

    }

    public double syncSubmit() {
        try {
            pipe.syncSubmit(data);
            return finalWeight;
        } catch (Exception e) {
            try {
                String home = System.getProperty("user.home");
                File f = new File(home + File.separator + "Documents" + File.separator + "Weight.txt");

                BufferedWriter out = new BufferedWriter(new FileWriter(f));
                try {
                    out.write("Error! " + e.getMessage());
                } finally {
                    out.close();
                }
            } catch (IOException exception) {
                System.out.println("An error occurred.");
                exception.printStackTrace();
            }
            return finalWeight;
        }

    }


    public void close() throws UsbException {
        try {
            pipe.close();
            iface.release();
            LibUsb.exit(context);
        } catch (Exception e) {
            try {
                String home = System.getProperty("user.home");
                File f = new File(home + File.separator + "Documents" + File.separator + "Weight.txt");

                BufferedWriter out = new BufferedWriter(new FileWriter(f));
                try {
                    out.write("Error! " + e.getMessage());
                } finally {
                    out.close();
                }
            } catch (IOException exception) {
                System.out.println("An error occurred.");
                exception.printStackTrace();
            }
        }

    }

    @Override
    public void dataEventOccurred(UsbPipeDataEvent upde) {
        if (data[2] == 12) { //This means it is in imperial Mode
            if (data[1] == 4) {
                int weight = (data[4] & 0xFF) + (data[5] << 8);
                int scalingFactor = data[3];
                finalWeight = scaleWeight(weight, scalingFactor); //final weight, applies to both metric and imperial
            }else if (data[1] == 5) {
                int weight = (data[4] & 0xFF) + (data[5] << 8);
                int scalingFactor = data[3];
                finalWeight = scaleWeight(weight, scalingFactor)*(-1); //final weight, applies to both metric and imperial
            } else if (data[1] == 2) {
                finalWeight = 0;
            }
        } else { //This would mean it is in metric
            if (data[1] == 4) {
                int weight = (data[4] & 0xFF) + (data[5] << 8);
                int scalingFactor = data[3];
                finalWeight = (scaleWeight(weight, scalingFactor)*2.20462); //final weight, applies to both metric and imperial
            } else if (data[1] == 5) {
                int weight = (data[4] & 0xFF) + (data[5] << 8);
                int scalingFactor = data[3];
                finalWeight = (scaleWeight(weight, scalingFactor)*2.20462)*(-1); //final weight, applies to both metric and imperial
            } else if (data[1] == 2) {
                finalWeight = 0;
            }

        }

    }

    private double scaleWeight(int weight, int scalingFactor) {
        return weight * Math.pow(10, scalingFactor);
    }


    @Override
    public void errorEventOccurred(UsbPipeErrorEvent usbPipeErrorEvent) {
        Logger.getLogger(UsbScale.class.getName()).log(Level.SEVERE, "Scale Error", usbPipeErrorEvent);
        try {
            String home = System.getProperty("user.home");
            File f = new File(home + File.separator + "Documents" + File.separator + "Weight.txt");

            BufferedWriter out = new BufferedWriter(new FileWriter(f));
            try {
                out.write("Error! " + usbPipeErrorEvent);
            } finally {
                out.close();
                System.exit(0);
            }
        } catch (IOException exception) {
            System.out.println("An error occurred.");
            exception.printStackTrace();
        }
    }
}

Thanks for any pointers in advance!

sweintritt
  • 366
  • 4
  • 10

1 Answers1

1

You have poor exception handling which means that the file of interest is written in many parts of the code so it is harder to narrow down exactly what your code is doing.

Instead, try this refactoring which may not solve your problem but will cut down the number of places you need to check and perhaps the location of the issue with be clearer:

  1. Add single variable for the file to write in Init.java:

    // Use Paths.get in older JVM
    Path f = Path.of(System.getProperty("user.home"), "Documents", "Weight.txt"); 
    
  2. Wipe all catch block exception handlers in UsbScale class and let the exception leave ALL methods. Change return null; or other errors to throw new LibUsbException("some message") to provide specific types of issues such as in UsbScale.findScale and UsbScale.findDevice.

  3. Make USBScale implement AutoCloseable so it can be used in try-with-resources

    public class UsbScale implements UsbPipeListener, AutoCloseable {
    
  4. Add flag isOpened=true in open(), and logic to close so it checks !isOpened

    public void open()  {
       isOpened = true;
       ...
    }
    private boolean isOpened = false;
    public void close() throws UsbException {
       if (!isOpened) return;
       isOpened =false;
       ...
    }
    
  5. Init therefore becomes much shorter and is the only place that writes the output file so debugging is easier. Use Files.writeString(f, message) rather than a verbose (and copied) new FileWriter(f) code block, and handle exceptions in this one method:

    public static void main(String[] args) throws IOException {
       String message = "[UNKNOWN]";
       Path f = Path.of(System.getProperty("user.home"), "Documents", "Weight.txt");
       try (UsbScale scale = UsbScale.findScale()){
           // scale is NEVER NULL AT THIS POINT if exceptions are handled correctly in UsbScale.findScale()
           // assert scale != null;
           scale.open();
           double weight = 0;
           while (weight == 0) {
               weight = scale.syncSubmit();
           }
           weight = Math.round(weight * 10d) / 10d;
           message = String.valueOf(weight);
       } catch (Exception e) {
           message = "Error! " + e.getMessage();
           e.printStackTrace();
       } finally {
           Files.writeString(f, message, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
       }
    

    }

  6. Finally, with 2 processes accessing same file, you may want to write to temporary file first and rename to target afterwards:

           Path tmp = Files.createTempFile(f.getParent(), "weight","tmp"); 
           Files.writeString(tmp, message, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
           Files.move(tmp, f);
    
DuncG
  • 12,137
  • 2
  • 21
  • 33
  • I mean the file is only written in one place each time the code executes if there is an exception it would be handled and the file would be written to once. Plus it's not bad for being written in a couple of hours... And I was planning on moving the file writer to its own method today as it cleans it up. Plus I have researched a little more and buffered writer needs to be flushed before closed. At least in the documentation is specified as such. Most of the error handling happens in the other application. I just need this one to make sure the file is written every execution. – Collin Short Sep 01 '22 at 13:17
  • Not true on buffered writer - flush it if you like but close is sufficient as close flushes implicitly. – DuncG Sep 01 '22 at 13:23
  • Hmm, hopefully cleaning the code up will help a bit as obviously in development you write functional first then error handle then quality control then you simplify the code. I guess I will proceed to the last step and see if that fixes it :/ – Collin Short Sep 01 '22 at 13:35
  • @DungG you would agree though correct? It would seem to be a memory leak of some kind? Possibly a stream isn't getting closed when it should? – Collin Short Sep 01 '22 at 13:40
  • A good starting point is switching to`Files.writeString` as that ensure file handling is consistent, and make sure you haven't accidentally launched multiple instances, and check there isn't some usb cleanup handling missing – DuncG Sep 01 '22 at 13:50
  • Got it. Is bufferedwrittere even necessary for my app? – Collin Short Sep 01 '22 at 13:53
  • You know, going through my code now... in the right mindset, it is pretty bad. – Collin Short Sep 01 '22 at 15:11
  • @CollinShort Correct, you can use `FileWriter` without `BufferedWriter` as you only use a single call to `write` followed by `close`. However `Files.writeString` is still better as it does everything you need in one step. – DuncG Sep 01 '22 at 16:29