Java Sound on a Raspberry Pi with OpenJDK

try(InputStream inputStream = new BufferedInputStream(getClass().getClassLoader().getResourceAsStream("beep.wav"));
    AudioInputStream audioIn = AudioSystem.getAudioInputStream(inputStream)) {
    AudioFormat format = audioIn.getFormat();
    DataLine.Info info = new DataLine.Info(Clip.class, format);
    Clip clip = (Clip) AudioSystem.getLine(info);
    clip.open(audioIn);
    clip.start();
    Thread.sleep(clip.getMicrosecondLength() / 1000);
} catch (UnsupportedAudioFileException | IOException | LineUnavailableException | InterruptedException  e1) {
    log.error("Error playing sound.", e1);
}

 

javax.sound.sampled.LineUnavailableException
 at org.classpath.icedtea.pulseaudio.PulseAudioMixer.openImpl(PulseAudioMixer.java:714)
 at org.classpath.icedtea.pulseaudio.PulseAudioMixer.openLocal(PulseAudioMixer.java:588)
 at org.classpath.icedtea.pulseaudio.PulseAudioMixer.openLocal(PulseAudioMixer.java:584)
 at org.classpath.icedtea.pulseaudio.PulseAudioMixer.open(PulseAudioMixer.java:579)
 at org.classpath.icedtea.pulseaudio.PulseAudioDataLine.open(PulseAudioDataLine.java:94)
 at org.classpath.icedtea.pulseaudio.PulseAudioDataLine.open(PulseAudioDataLine.java:283)
 at org.classpath.icedtea.pulseaudio.PulseAudioClip.open(PulseAudioClip.java:402)
 at org.classpath.icedtea.pulseaudio.PulseAudioClip.open(PulseAudioClip.java:453)

 

I was pretty bummed when my sound stopped playing on my Raspberry Pi when I switched to OpenJDK 8 after originally using Oracle JDK 8.

Editing the sound.properties for the OpenJDK solved my problem.

sudo vim /etc/java-8-openjdk/sound.properties

 

Comment out the icedtea classpath configs:

#javax.sound.sampled.Clip=org.classpath.icedtea.pulseaudio.PulseAudioMixerProvider
#javax.sound.sampled.Port=org.classpath.icedtea.pulseaudio.PulseAudioMixerProvider
#javax.sound.sampled.SourceDataLine=org.classpath.icedtea.pulseaudio.PulseAudioMixerProvider
#javax.sound.sampled.TargetDataLine=org.classpath.icedtea.pulseaudio.PulseAudioMixerProvider

 

Remove the comments from the sun classpath configs:

javax.sound.sampled.Clip=com.sun.media.sound.DirectAudioDeviceProvider
javax.sound.sampled.Port=com.sun.media.sound.PortMixerProvider
javax.sound.sampled.SourceDataLine=com.sun.media.sound.DirectAudioDeviceProvider
javax.sound.sampled.TargetDataLine=com.sun.media.sound.DirectAudioDeviceProvider

 

Problem solved.

SaltStack – Open an reverse SSH tunnel to a Raspberry Pi minion from the salt-master in AWS

Executing salt modules and states on minions is the normal way to interact with a minion, but sometimes it is a lot faster to manage a remote machine with a terminal shell over ssh. If that machine is behind a NAT, then it gets more difficult to ssh to that box, but if you control the salt master then you can setup a reverse SSH tunnel to the minion. Here’s how I did it from an AWS Lightsail salt-master and a Raspberry Pi without touching the minion except to set it up as a salt minion to my master. At some point I will likely turn this into a salt state so it can executed faster.

// It'd be better security-wise if you used a separate pivot machine instead of the salt master
// Generate an ssh key pair for a user on your salt-master
 ssh-keygen

// Copy your ssh public key to the salt-master file root
 sudo mkdir /srv/salt/ssh_keys
 sudo cp /home/ubuntu/.ssh/id_rsa.pub /srv/salt/ssh_keys/ubuntu.id_rsa.pub

// Create a new, limited user on your salt-master
// Ideally you would lock this user down as much as possible, 
// but still allow it to ssh to the salt-master and open up a reverse ssh tunnel.
 sudo useradd -m sshtunnel -s /bin/bash

// Generate ssh keys for sshtunnel
 sudo ssh-keygen -f sshtunnel

// Copy the key to your salt file root
 sudo cp sshtunnel /srv/salt/ssh_keys/sshtunnel.id_rsa

// Add they public key in sshtunnel.pub to authorized_keys file
 sudo cp sshtunnel.pub /home/sshtunnel/.ssh/authorized_keys
 sudo chown -R sshtunnel:sshtunnel /home/sshtunnel/.ssh

// Add your salt-master user's public ssh key to the 
// minion's /home/pi/.ssh/authorized_keys
 sudo salt minion01 ssh.set_auth_key_from_file pi salt://ssh_keys/ubuntu.id_rsa.pub

// Place salt master fingerprint in /home/pi/.ssh/known_hosts
 sudo salt minion01 ssh.set_known_host pi salt-master.example.com

// Generate a md5 sum of /srv/salt/ssh_keys/sshtunnel.id_rsa because 
// you'll need it when placing the file on the minion in the next command
 sudo md5sum /srv/salt/ssh_keys/sshtunnel.id_rsa

// Place ssh key from the master's /home/sshtunnel/.ssh/id_rsa on 
// the minion at /home/pi/.ssh/id_rsa
 sudo salt minion01 file.manage_file /home/pi/.ssh/id_rsa '' '{}' salt://ssh_keys/sshtunnel.id_rsa '{hash_type: 'md5', 'hsum': ''}' pi pi '600' base ''

//////////   MAKE THE CONNECTION ////////////////
// Open SSH Tunnel from the minion to the master with a port forward
 sudo salt minion01 cmd.run runas=pi 'ssh -f -R 7000:localhost:22 sshtunnel@salt-master.example.com sleep 30' --async
// Connect from the master to the minion on the local port that was opened
 ssh pi@localhost -p 7000
////////////////////////////////////////////////

// You are now connected to the minion and can manage and debug things faster
// Once you quit the ssh session, the minion->master ssh session will close

// Cleanup to avoid giving minions access to the master
// Remove ssh key from /home/pi/.ssh/id_rsa
 sudo salt minion01 file.remove /home/pi/.ssh/id_rsa

Pi4J – ADC MCP3008 – SPI – Sensor Reader Example


import com.pi4j.io.spi.SpiChannel;
import com.pi4j.io.spi.SpiDevice;
import com.pi4j.io.spi.SpiFactory;

import java.nio.ByteBuffer;
import java.io.IOException;

public class SpiMCP3008 {

    public static SpiDevice spi = null;
    public static byte INIT_CMD = (byte) 0xD0; // 11010000

    public static void main(String args[]) throws InterruptedException, IOException {
        System.out.println("<--Pi4J--> SPI test program using MCP3008 AtoD Chip");

        spi = SpiFactory.getInstance(SpiChannel.CS0,
                                     SpiDevice.DEFAULT_SPI_SPEED, // default spi speed 1 MHz
                                     SpiDevice.DEFAULT_SPI_MODE); // default spi mode 0

        while(true) {
            read(0); // Read channel 1
			read(1); // Read channel 2
            Thread.sleep(100);
        }
    }

    public static void read(int channel) throws IOException {
        // 10-bit ADC MCP3008
        byte packet[] = new byte[3];
        packet[0] = 0x01;  // INIT_CMD;  // address byte
        packet[1] = (byte) ((0x08 + channel) << 4);  // singleEnded + channel
        packet[2] = 0x00;
           
        byte[] result = spi.write(packet);
        System.out.println( ((result[1] & 0x03 ) << 8) | (result[2] & 0xff) );
    }
}

Pi4J – DAC MCP4725 – I2C Example – Variable Voltage for a SyRen Motor Controller


import com.pi4j.io.i2c.I2CBus;
import com.pi4j.io.i2c.I2CDevice;
import com.pi4j.io.i2c.I2CFactory;

import java.io.IOException;

public class I2CTest {

    public static void main(String[] args) throws Exception {
        System.out.println("Starting:");

        final I2CBus bus = I2CFactory.getInstance(I2CBus.BUS_1);

        MotorController motorController = new MotorController(bus);
        motorController.init();

        while (true) {
	    motorController.write(1024);
            Thread.sleep(5000);
            motorController.write(2048);
            Thread.sleep(5000);
            motorController.write(3064);
            Thread.sleep(5000);
        }

    }

    public static class MotorController {

        private I2CDevice device;

	int REG_WRITEDAC = 0x40;
        int REG_WRITEDACEEPROM = 0x60;

        public MotorController(I2CBus bus) throws IOException {
            device = bus.getDevice(0x62); // MCP4725
        }

        public void write(int voltage) throws IOException {
            // 12-bit DAC
	    if (voltage > 4095) {
                voltage = 4095;
            }
            if (voltage < 0) {
                voltage = 0;
            }
            System.out.println("Setting voltage to: " + voltage);
            // Value needs to be left-shifted four bytes for the MCP4725
            byte[] bytes = {(byte)((voltage >> 4) & 0xFF),(byte)((voltage << 4) & 0xFF)};
            device.write(REG_WRITEDACEEPROM, bytes, 0, 2);
        }

        public int read() throws IOException {
            byte[] buf = new byte[256];
            int res = device.read(0, buf, 0, 6);
            return 0;
        }

    }

}

Throttled Producers and Consumers in Java

BlockingQueue queue = new ArrayBlockingQueue<>(QUEUE_MAXIMUM_SIZE);

producers:  queue.put(dbObject);

consumers: while (RUNNING) {  DBObject dbObject = queue.poll(1, TimeUnit.SECONDS); ...

When transferring large volumes of data from one type of database (Mongo for example), to another (DynamoDB), the fastest way is to do things in parallel. Reading from one Mongo cursor from one Mongo server in the cluster gives you limited performance. Writing to DynamoDB on one thread maxes out quickly to something like 100/s. I ended up creating many threads to read from Mongo in parallel on defined partitions to utilize all of the shards and replica sets in the cluster. I had these threads put items on the queue. Setting a max size on the BlockingQueue would throttle my reads to not flood the consumers and run out of memory. The consumers would read from the queue and eventually be told to stop when all of the producers were done. This model was very performant and easy to monitor. By monitoring the size of the queue, I could see if the consumers or producers were working faster. With the BlockingQueue, the producers were not allowed to out-pace the consumers. The consumers didn’t have any problems with this though since DynamoDB is way more scalable than Mongo.

AWS DynamoDB map object to Base64 encoded gzipped JSON in Java


Annotation in a DynamoDBTable class:
    @DynamoDBAttribute
    @DynamoDBMarshalling(marshallerClass=PojoMarshaller.class)
    private Pojo pojo;

public static class PojoMarshaller extends GzipJsonMarshaller<Pojo> { }

-------------------------------------------------------------------------------------------------------------

import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMarshaller;
import com.fasterxml.jackson.databind.*;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;

import java.io.*;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.zip.*;

import static com.amazonaws.util.Throwables.failure;

public class GzipJsonMarshaller<T extends Object> implements DynamoDBMarshaller<T> {

    private static final ObjectMapper mapper = new ObjectMapper();
    private static final ObjectWriter writer = mapper.writer();

    @Override
    public String marshall(T obj) {
        try {
            String plainJsonString = writer.writeValueAsString(obj);
            byte[] binaryBytes = compressString(plainJsonString).array();
            String base64BinaryString = Base64.encodeBase64String(binaryBytes);
            return base64BinaryString;
        } catch (Exception e) {
            throw failure(e, "Unable to marshall the instance of " + obj.getClass() + "into a string");
        }
    }

    @Override
    public T unmarshall(Class<T> clazz, String base64BinaryString) {
        try {
            byte[] binaryBytes = Base64.decodeBase64(base64BinaryString);
            String plainJsonString = uncompressString(ByteBuffer.wrap(binaryBytes));
            return mapper.readValue(plainJsonString, clazz);
        } catch (Exception e) {
            throw failure(e, "Unable to unmarshall the string " + base64BinaryString + "into " + clazz);
        }
    }

    public static ByteBuffer compressString(String input) throws IOException {
        ByteArrayOutputStream byteArrayOutput = new ByteArrayOutputStream();
        GZIPOutputStream gzipOutput = new GZIPOutputStream(byteArrayOutput);
        gzipOutput.write(input.getBytes("UTF-8"));
        gzipOutput.finish();
        byte[] compressedBytes = byteArrayOutput.toByteArray();
        ByteBuffer buffer = ByteBuffer.wrap(compressedBytes);
        return buffer;
    }

    public static String uncompressString(ByteBuffer input) throws IOException {
        byte[] bytes = input.array();
        ByteArrayInputStream byteArrayInput = new ByteArrayInputStream(bytes);
        GZIPInputStream gzipInput = new GZIPInputStream(byteArrayInput);
        ByteArrayOutputStream byteArrayOutput = new ByteArrayOutputStream();
        IOUtils.copy(gzipInput, byteArrayOutput);
        return new String(byteArrayOutput.toByteArray(), "UTF-8");
    }

}


Convert Mongo DBObject to POJO with Jongo


import org.jongo.ResultHandler;
import org.jongo.bson.Bson;
import org.jongo.bson.BsonDocument;
import org.jongo.marshall.Unmarshaller;
import org.jongo.marshall.jackson.JacksonEngine;
import org.jongo.marshall.jackson.configuration.Mapping;

...

    public Pojo getPojo(DBObject dbObject) {
        JacksonEngine engine = new JacksonEngine(new Mapping.Builder().build());
        ResultHandler<Pojo> handler = new UnmarshallingResultHandler<>(engine, Pojo.class); 
        Pojo pojo = handler.map(dbObject);
        return pojo;
    }

    public static class UnmarshallingResultHandler<T> implements ResultHandler<T> {
        private final Unmarshaller unmarshaller;
        private final Class<T> clazz;
        public UnmarshallingResultHandler(Unmarshaller unmarshaller, Class<T> clazz) {
            this.unmarshaller = unmarshaller;
            this.clazz = clazz;
        }
        public T map(DBObject result) {
            BsonDocument bsonDocument = Bson.createDocument(result);
            return unmarshaller.unmarshall(bsonDocument, clazz);
        }
    }