Related
I'm working with a recently purchased DragonBoard 410C running the Android 5.1 operating system and using Android Studio with Kotlin for generating a sample application that explores some of the hardware such as the 40 pin low power connector.
My question is how to access the 40 pin low power connector with the GPIO pins using Kotlin and Android Studio.
From my research thus far, it appears that the mraa library is the path to success however I have been unable to find any documentation on using the library with Kotlin.
How do I get started with the mraa library with Kotlin to access the 40 pin low power connector?
Or is there a different approach?
My first example is a simple blink the LED application however I have no idea as to how to access pins of the low power connector using Kotlin.
Notes and resources
mraa documentation page
Libmraa is a C/C++ library with bindings to Python, Javascript and
Java to interface with the I/O on Galileo, Edison & other platforms,
with a structured and sane API where port names/numbering matches the
board that you are on. Use of libmraa does not tie you to specific
hardware with board detection done at runtime you can create portable
code that will work across the supported platforms.
upm library for mraa GitHub repository
The UPM repository provides software drivers for a wide variety of
commonly used sensors and actuators. These software drivers interact
with the underlying hardware platform (or microcontroller), as well as
with the attached sensors, through calls to MRAA APIs.
Which Android runs which Linux kernel? https://android.stackexchange.com/questions/51651/which-android-runs-which-linux-kernel
Android Version |API Level |Linux Version in AOSP |Header Version
----------------------------------------------------------------------------------------
4.4 Kit Kat |19, 20 |(3.10) |2.6.18
5.x Lollipop |21, 22 |(3.16.1) |3.14.0
6.0 Marshmallow |23 |(3.18.10) |3.18.10
sysfs is dead! long live libgpiod! (libgpiod for linux & CircuitPython)
This is basically code that will replace our Python DHT driver, and
has the benefit of being forward compatible with any other Linux board
that runs a 4.8+ kernel. We’ll slowly be replacing other CircuitPython
code to use libgpiod, so that we can have broad support for
CircuitPython on a Raspberry Pi, BeagleBone or Onion.io.
There’s not a lot of libgpiod code out there, and libgpiod doesn’t
come stock on Linux distros yet which may be why its taking a little
while to catch on. There’s bindings for C and Python. Here’s a script
that can help you get started by compiling it for you
https://github.com/adafruit/Raspberry-Pi-Installer-Scripts/blob/master/libgpiod.sh
A DragonBoard 410C GPIO library on GitHub https://github.com/IOT-410c/DragonBoard410c_GpioLibrary which is written in Java and uses the "/sys/class/gpio" method of Linux to access GPIO pins. This looks like this is a repository in a set of repositories used for Coursera courses on Internet of things, some of which use the DragonBoard 410C.
The Linux Kernel: Legacy GPIO Interfaces
This provides an overview of GPIO access conventions on Linux.
These calls use the gpio_* naming prefix. No other calls should use
that prefix, or the related _gpio* prefix.
Android Studio and adb
Android Studio is the application for developing Android apps. It can be downloaded for installation from https://developer.android.com/studio/releases
In addition there are the Android Platform Tools which are a separate download. The adb shell application is part of these tools. The tools can be downloaded for installation from SDK Platform Tools release notes. Pick the particular version needed for your operating system (Windows, Linux, MacOS).
Android Things
While Android Things looks helpful, it appears only a couple of boards are actually supported and the DragonBoard 410C is not one of those. And I'm not sure that Android Things would work with Android 5.1 anyway.
https://developer.android.com/things/get-started
However there was a Brillo (now Android Things) port for DragonBoard 410C https://discuss.96boards.org/t/android-things-on-the-dragonboard/1128
Android Developers > Docs > Android Things > Guides > GPIO
In order to open a connection to a GPIO port, you need to know the
unique port name. During the initial stages of development, or when
porting an app to new hardware, it's helpful to discover all the
available port names from PeripheralManager using getGpioList():
Android Things GitHub repositories https://github.com/androidthings/
See also the following stackoverflow posts which have something to say on this subject. See tag [android-things] in stackoverflow as well.
How can a GpioCallback register "false" twice in a row?
Android Things Pin naming convention
PeripheralManagerService throws NoClassDefFoundError
After reviewing a number of alternatives, it appears that the easiest approach for accessing the GPIO pins of the DragonBoard 410C running Android 5.1 was to use the legacy sysfs special device files method.
I'm not sure if this is the only workable solution. Using Android Things as well as using libgpiod both seem to require a more recent Linux kernel than Android 5.1 uses.
I have written an article on CodeProject.com providing details about working up this solution. See Using Windows 10 for Development with DragonBoard 410C and Android.
Which Android runs which Linux kernel? https://android.stackexchange.com/questions/51651/which-android-runs-which-linux-kernel
Android Version |API Level |Linux Version in AOSP |Header Version
----------------------------------------------------------------------------------------
4.4 Kit Kat |19, 20 |(3.10) |2.6.18
5.x Lollipop |21, 22 |(3.16.1) |3.14.0
6.0 Marshmallow |23 |(3.18.10) |3.18.10
This method also seems to be the easiest in that the library used is written in Kotlin as well.
Using the legacy sysfs special device GPIO interface
See this StackOverFlow post about Linux pseudo files and special device files and the legacy sysfs interface for GPIO pins, what is the /sys/class/gpio/export and `/sys/class/gpio/unexport mechanism and what is the underlying sysfs functionality? .
I found a simple GPIO library written in Java that provided necessary source code. Android Studio has a tool that converted the Java to Kotlin which I included into my project. The source code is below in files Gpio.kt and GpioProcessor.kt.
However in order for this to work I had to make a change to my DragonBoard startup scripts in order to make sure that the necessary special device files were created and available with the proper permissions allowing a user program to manipulate the GPIO pins.
The following procedure is from the Coursera class Internet of Things: Sensing and Actuation from Devices, Lesson 5a: Access GPIO through programs (Android) video #2, Modify boot script. The procedure was to:
use adb to pull a copy of /etc/init.qcom.post_boot.sh from the DragonBoard to my PC
use Notepad to modify the shell script to create the special device files
use adb to push the modified copy back to the Dragonboard
use adb to reboot the DragonBoard
The additional shell code to add to the bottom of /etc/init.qcom.post_boot.sh is as follows. However these special device files are for Android 5.1 only. Linux uses different GPIO pin names.
set -A pins 938 915 1017 926 937 930 914 971 901 936 935
for i in 0 1 2 3 4 5 6 7 8 9 10
do
echo ${pins[i]} > /sys/class/gpio/export;
chmod 777 /sys/class/gpio/gpio${pins[i]};
chmod 777 /sys/class/gpio/gpio${pins[i]}/value;
chmod 777 /sys/class/gpio/gpio${pins[i]}/direction;
done
A Note on sysfs device attributes
Here is some documentation on the GPIO Sysfs Inferface for Userspace from kernel.org. In addition to the two attributes that I use, direction and value, there are several others such as edge and active_low.
“direction” … reads as either “in” or “out”. This value may normally
be written. Writing as “out” defaults to initializing the value as
low. To ensure glitch free operation, values “low” and “high” may be
written to configure the GPIO as an output with that initial value.
Note that this attribute will not exist if the kernel doesn’t support
changing the direction of a GPIO, or it was exported by kernel code
that didn’t explicitly allow userspace to reconfigure this GPIO’s
direction.
“value” … reads as either 0 (low) or 1 (high). If the GPIO is
configured as an output, this value may be written; any nonzero value
is treated as high.
If the pin can be configured as interrupt-generating interrupt and if
it has been configured to generate interrupts (see the description of
“edge”), you can poll(2) on that file and poll(2) will return whenever
the interrupt was triggered. If you use poll(2), set the events
POLLPRI and POLLERR. If you use select(2), set the file descriptor in
exceptfds. After poll(2) returns, either lseek(2) to the beginning of
the sysfs file and read the new value or close the file and re-open it
to read the value.
“edge” … reads as either “none”, “rising”, “falling”, or “both”. Write
these strings to select the signal edge(s) that will make poll(2) on
the “value” file return.
This file exists only if the pin can be configured as an interrupt
generating input pin.
“active_low” … reads as either 0 (false) or 1 (true). Write any
nonzero value to invert the value attribute both for reading and
writing. Existing and subsequent poll(2) support configuration via the
edge attribute for “rising” and “falling” edges will follow this
setting.
Kotlin source code for using sysfs
The complete testing application I am using to explore this topic of using the DragonBoard 410C with Android is in my GitHub repository, https://github.com/RichardChambers/dragonboard_410c
Source for file Gpio.kt
package com.example.myapplication
import java.io.*
/**
* Created by Ara on 7/21/15.
* From https://www.instructables.com/id/DragonBoard-How-to-Access-GPIOs-Using-Java/
* Java source from the article was converted to Kotlin using Android Studio.
*
* See as well https://github.com/IOT-410c/DragonBoard410c_GpioLibrary
*
*/
class Gpio(pin: Int) {
private val pin: Int
/*
* The GPIO pins are represented by folders in the Linux file system
* within the folder /sys/class/gpio. Each pin is represented by a folder
* whose name is the prefix "gpio" followed by the pin number.
* Within the folder representing the pin are two files, "value" used to
* set or get the value of the pin and "direction" used to set or get
* the direction of the pin.
*
* This function creates the path to the Linux file which represents a particular
* GPIO pin function, "value" or "direction".
*/
private fun MakeFileName(pin: Int, op: String): String {
return "/sys/class/gpio/gpio$pin$op"
}
/*
* Get or set the current direction of a pin.
* A pin may be either an Input pin or an Output pin.
*/
var direction: String
get() {
println("Getting Direction")
var line = ""
try {
val br = BufferedReader(FileReader(MakeFileName(pin, "/direction")))
line = br.readLine()
br.close()
} catch (e: Exception) {
println("Error: " + e.message)
}
return line
}
private set(direction) {
println("Setting Direction")
try {
val out = BufferedWriter(FileWriter(MakeFileName(pin, "/direction"), false))
out.write(direction)
out.close()
} catch (e: IOException) {
println("Error: " + e.message)
}
}
/**
* Get or Set pin value.
* #param value Value of pin.
* 0 -> Low Level.
* 1 -> High Level
*/
var value: Int
get() {
println("Getting Value")
var line = ""
try {
val br = BufferedReader(FileReader(MakeFileName(pin, "/value")))
line = br.readLine()
br.close()
} catch (e: Exception) {
println("Error: " + e.message)
}
return line.toInt()
}
private set(value) {
println("Setting Value")
try {
val out = BufferedWriter(FileWriter(MakeFileName(pin, "/value"), false))
out.write(Integer.toString(value))
out.close()
} catch (e: IOException) {
println("Error: " + e.message)
}
}
/**
* Set pin as high.
*/
fun pinHigh() {
value = HIGH
}
/**
* Set pin as low.
*/
fun pinLow() {
value = LOW
}
/**
* Set pin as output.
*/
fun pinOut() {
direction = "out"
}
/**
* Set pin as input.
* #param pin - Desirable pin.
*/
fun pinIn() {
direction = "in"
}
fun exportPin() {
println("Exporting Ping")
try {
val out = BufferedWriter(FileWriter("$PATH/export", false))
out.write(pin.toString())
out.close()
} catch (e: IOException) {
println("Error: " + e.message)
}
}
/**
* Disable access to GPIO.
* #param pin GPIO pin to disable access.
*/
fun unexportPin() {
println("unExporting Ping")
try {
val out = BufferedWriter(FileWriter("$PATH/unexport", false))
out.write(pin.toString())
out.close()
} catch (e: IOException) {
println("Error: " + e.message)
}
}
companion object {
const val HIGH = 1
const val LOW = 0
private const val PATH = "/sys/class/gpio"
}
/**
* Set desirable pin for the GPIO class.
*/
init {
println("Initializing pin $pin")
this.pin = pin
}
}
Source for GpioProcessor.kt
package com.example.myapplication
import java.io.BufferedWriter
import java.io.FileWriter
import java.io.IOException
import java.util.*
/**
* Created by Ara on 7/21/15.
* From https://www.instructables.com/id/DragonBoard-How-to-Access-GPIOs-Using-Java/
* Java source from the article was converted to Kotlin using Android Studio.
*
* See as well https://github.com/IOT-410c/DragonBoard410c_GpioLibrary
*
* Simple example main()
*
* public class Main {
*
* public static void main(String[] args) {
* int count = 0;
* int buttonValue = 0;
*
* GpioProcessor gpioProcessor = new GpioProcessor();
*
* // Get reference of GPIO27 and GPIO29.
*
* Gpio gpioPin27 = gpioProcessor.getPin27();
* Gpio gpioPin29 = gpioProcessor.getPin29();
*
* // Set GPIO27 as output.Set GPIO29 as input.
* gpioPin27.pinOut();
* gpioPin29.pinIn();
*
* while(count<20){
* count++;
* // Read value of GPIO29.
* buttonValue=gpioPin29.getValue();
*
* if(buttonValue == 0){
* // Set GPIO27 as low level.
* gpioPin27.pinLow();
* } else{
* // Set GPIO27 as high level.
* gpioPin27.pinHigh();
* }
*
* try {
* Thread.sleep(1000);
* } catch(InterruptedException e){
* // TODO Auto-generated catch block
* e.printStackTrace();
* }
* }
*
* // Disable access GPIO27 and GPIO29.
* gpioProcessor.closePins();
* }
* }
*/ /*
This class abstracts the use of the gpio pins. This class can be utilized on any linux operating
system that has gpio pins defined in the /sys/class/gpio directory. It is required that the gpio
pins themselves are available for access by the user of this application, and may require a
change of permissions.
*/
class GpioProcessor {
private val PATH = "/sys/class/gpio"
private val pins: MutableList<Int> = ArrayList()
// mapping of physical pin number to GPIO file number.
// the mapping varies depending on the operating system
private val androidPin23 = 938
private val androidPin24 = 914
private val androidPin25 = 915
private val androidPin26 = 971
private val androidPin27 = 1017
private val androidPin28 = 901 // GPIO pin borrowed from MPP. supports PWM. support analog I/O.
private val androidPin29 = 926 // (input only)
private val androidPin30 = 927
private val androidPin31 = 937
private val androidPin32 = 936
private val androidPin33 = 930
private val androidPin34 = 935
private val linuxPin23 = 36
private val linuxPin24 = 12
private val linuxPin25 = 13
private val linuxPin26 = 69
private val linuxPin27 = 115
private val linuxPin28 = 4 // GPIO pin borrowed from MPP. supports PWM. support analog I/O.
private val linuxPin29 = 24 // (input only)
private val linuxPin30 = 25
private val linuxPin31 = 35
private val linuxPin32 = 34
private val linuxPin33 = 28
private val linuxPin34 = 33
private val physicalPin23 = androidPin23
private val physicalPin24 = androidPin24
private val physicalPin25 = androidPin25
private val physicalPin26 = androidPin26
private val physicalPin27 = androidPin27
private val physicalPin28 = androidPin28 // GPIO pin borrowed from MPP. supports PWM. support analog I/O.
private val physicalPin29 = androidPin29 // (input only)
private val physicalPin30 = androidPin30
private val physicalPin31 = androidPin31
private val physicalPin32 = androidPin32
private val physicalPin33 = androidPin33
private val physicalPin34 = androidPin34
/**
* Get function of specific pin.
* #param pin Desirable pin.
*/
fun getPin(pin: Int): Gpio {
exportPin(pin)
pins.add(pin)
return Gpio(pin)
}
/**
* Get pin 23;
* #returns {Gpio}
*/
val pin23: Gpio
get() = getPin(physicalPin23)
/**
* Get pin 24.
* #returns {Gpio}
*/
val pin24: Gpio
get() = getPin(physicalPin24)
/**
* Get pin 25.
* #returns {Gpio}
*/
val pin25: Gpio
get() = getPin(physicalPin25)
/**
* Get pin 26.
* #returns {Gpio}
*/
val pin26: Gpio
get() = getPin(physicalPin26)
/**
* Get pin 27.
* #returns {Gpio}
*/
val pin27: Gpio
get() = getPin(physicalPin27)
/**
* Get pin 28.
* #returns {Gpio}
*/
val pin28: Gpio
get() = getPin(physicalPin28)
/**
* Get pin 29.
* #returns {Gpio}
*/
val pin29: Gpio
get() = getPin(physicalPin29)
/**
* Get pin 30.
* #returns {Gpio}
*/
val pin30: Gpio
get() = getPin(physicalPin30)
/**
* Get pin 31.
* #returns {Gpio}
*/
val pin31: Gpio
get() = getPin(physicalPin31)
/**
* Get pin 32.
* #returns {Gpio}
*/
val pin32: Gpio
get() = getPin(physicalPin32)
/**
* Get pin 33.
* #returns {Gpio}
*/
val pin33: Gpio
get() = getPin(physicalPin33)
/**
* Get pin 34.
* #returns {Gpio}
*/
val pin34: Gpio
get() = getPin(physicalPin34)
/**
* Get all GPIO's pins.
* #return List of pins.
*/
val allPins: Array<Gpio?>
get() {
val allPins = arrayOfNulls<Gpio>(12) // android linux
allPins[0] = pin23 // GPIO 938 GPIO 36
allPins[1] = pin24 // GPIO 914 GPIO 12
allPins[2] = pin25 // GPIO 915 GPIO 13
allPins[3] = pin26 // GPIO 971 GPIO 69
allPins[4] = pin27 // GPIO 1017 GPIO 115
allPins[5] = pin28 // Reserved
allPins[6] = pin29 // GPIO 926 GPIO 24 (input only)
allPins[7] = pin30 // GPIO 927 GPIO 25
allPins[8] = pin31 // GPIO 937 GPIO 35
allPins[9] = pin32 // GPIO 936 GPIO 34
allPins[10] = pin33 // GPIO 930 GPIO 28
allPins[11] = pin34 // GPIO 935 GPIO 33
return allPins
}
/**
* Enable access to GPIO.
* #param pin GPIO pin to access.
*/
private fun exportPin(pin: Int) {
println("Exporting Ping")
try {
val out = BufferedWriter(FileWriter("$PATH/export", false))
out.write(pin.toString())
out.close()
} catch (e: IOException) {
println("Error: " + e.message)
}
}
/**
* Disable access to GPIO.
* #param pin GPIO pin to disable access.
*/
private fun unexportPin(pin: Int) {
println("unExporting Ping")
try {
val out = BufferedWriter(FileWriter("$PATH/unexport", false))
out.write(pin.toString())
out.close()
} catch (e: IOException) {
println("Error: " + e.message)
}
}
fun closePins() {
for (pin in pins) {
unexportPin(pin)
}
pins.clear()
}
companion object {
const val TAG = "GpioProcessor"
}
}
Example source using the GpioProcessor class
I used the GPIO sysfs interface library in an Android app within a fragment by linking a button press to a listener. I have two buttons, one to turn the LED on by driving a pin high and a second to turn the LED off by driving a pin low.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<Button>(R.id.button_second).setOnClickListener {
findNavController().navigate(R.id.action_SecondFragment_to_FirstFragment)
}
val txtScroll = view.findViewById(R.id.LedStatus) as TextView
// find the button whose id is button_Location and then set an listener for
// any clicks on that button. In the following listener we are going to have
// the "Location" button, defined in the file fragment_first.xml, generate a
// list of the GPS service providers by creatinga LocationManager object to
// generate a list.
val gpioProcessor_x = GpioProcessor()
// Get reference of GPIO23.
val gpioPin23_x = gpioProcessor_x.pin23
gpioPin23_x.exportPin()
view.findViewById<Button>(R.id.button_led_off).setOnClickListener {
val gpioProcessor = GpioProcessor()
// Get reference of GPIO27.
val gpioPin23 = gpioProcessor.pin23
// Set GPIO23 as output.
gpioPin23.pinOut()
gpioPin23.pinLow() // drive pin low to turn off LED.
txtScroll.append("LED Off\n")
}
view.findViewById<Button>(R.id.button_led_on).setOnClickListener {
val gpioProcessor = GpioProcessor()
// Get reference of GPIO27.
val gpioPin23 = gpioProcessor.pin23
// Set GPIO23 as output.
gpioPin23.pinOut()
gpioPin23.pinHigh() // drive pin high to turn on LED
txtScroll.append("LED On\n")
}
}
Can someone provide an example of how to use the new AlwaysOnHotwordDetector class in Android?
I'd like to build an app, that when the app is running in the background, can detect a hotword like "next", or "back", or "pause".
Unless I have a huge blind spot, I don't think third-party applications can make use of this API. Its strange that AlwaysOnHotwordDetector (and related classes VoiceInteractionService etc.) have been granted public access.
If you are building a privileged app, look through these test projects from AOSP:
Voice Interaction - Basic AlwaysOnHotwordDetector usage/implementation
http://androidxref.com/5.0.0_r2/xref/frameworks/base/tests/VoiceInteraction/
Voice Enrollment - http://androidxref.com/5.0.0_r2/xref/frameworks/base/tests/VoiceEnrollment/
While trying to make this work, I came upon this:
AlwaysOnHotwordDetector's constructor:
/**
* #param text The keyphrase text to get the detector for.
* #param locale The java locale for the detector.
* #param callback A non-null Callback for receiving the recognition events.
* #param voiceInteractionService The current voice interaction service.
* #param modelManagementService A service that allows management of sound models.
*
* #hide
*/
public AlwaysOnHotwordDetector(String text, Locale locale, Callback callback,
KeyphraseEnrollmentInfo keyphraseEnrollmentInfo,
IVoiceInteractionService voiceInteractionService,
IVoiceInteractionManagerService modelManagementService) {
mText = text;
mLocale = locale;
mKeyphraseEnrollmentInfo = keyphraseEnrollmentInfo;
mKeyphraseMetadata = mKeyphraseEnrollmentInfo.getKeyphraseMetadata(text, locale);
mExternalCallback = callback;
mHandler = new MyHandler();
mInternalCallback = new SoundTriggerListener(mHandler);
mVoiceInteractionService = voiceInteractionService;
mModelManagementService = modelManagementService;
new RefreshAvailabiltyTask().execute();
}
The statement of interest here is:
mKeyphraseMetadata = mKeyphraseEnrollmentInfo.getKeyphraseMetadata(text, locale);
What does KeyphraseEnrollmentInfo#getKeyphraseMetadata(String, Locale) do?
/**
* Gets the {#link KeyphraseMetadata} for the given keyphrase and locale, null if any metadata
* isn't available for the given combination.
*
* #param keyphrase The keyphrase that the user needs to be enrolled to.
* #param locale The locale for which the enrollment needs to be performed.
* This is a Java locale, for example "en_US".
* #return The metadata, if the enrollment client supports the given keyphrase
* and locale, null otherwise.
*/
public KeyphraseMetadata getKeyphraseMetadata(String keyphrase, Locale locale) {
if (mKeyphrases == null || mKeyphrases.length == 0) {
Slog.w(TAG, "Enrollment application doesn't support keyphrases");
return null;
}
for (KeyphraseMetadata keyphraseMetadata : mKeyphrases) {
// Check if the given keyphrase is supported in the locale provided by
// the enrollment application.
if (keyphraseMetadata.supportsPhrase(keyphrase)
&& keyphraseMetadata.supportsLocale(locale)) {
return keyphraseMetadata;
}
}
Slog.w(TAG, "Enrollment application doesn't support the given keyphrase/locale");
return null;
}
At this point, my example project kept telling me: Enrollment application doesn't support keyphrases. So, digging a bit further - to support keyphrases, we need to provide additional meta-data:
// Taken from class KeyphraseEnrollmentInfo
/**
* Name under which a Hotword enrollment component publishes information about itself.
* This meta-data should reference an XML resource containing a
* <code><{#link
* android.R.styleable#VoiceEnrollmentApplication
* voice-enrollment-application}></code> tag.
*/
private static final String VOICE_KEYPHRASE_META_DATA = "android.voice_enrollment";
Additionally, we will need the android.permission.MANAGE_VOICE_KEYPHRASES permission. This is where the example project gets stuck:
<!-- Must be required by hotword enrollment application,
to ensure that only the system can interact with it.
#hide <p>Not for use by third-party applications.</p> -->
<permission android:name="android.permission.MANAGE_VOICE_KEYPHRASES"
android:label="#string/permlab_manageVoiceKeyphrases"
android:description="#string/permdesc_manageVoiceKeyphrases"
android:protectionLevel="signature|system" />
The permission required to support hotword detection is not available to third-party applications. I still can't figure out why package android.service.voice package has public access. Perhaps I am missing something here.
I'm trying to get data from a monitor to an Android application and I've took the IHE - PCD-01 transaction as a model.
The scheme is simple, is based on achieve the interconnection between the monitor and the tablet, where the monitor sends constantly information and the application is listening.
But what I don't understand is if I need an ACK or not after every message. Does anyone can help me with this?
TL;DR yes, nothing special here, support the usual HL7 ACK/NACK driven by MSH-15, MSH-16 fields. ACK-ing everything by default is "better safe then sorry"
The document "IHE Patient Care Device (PCD), Technical Framework, Volume 2 (PCD TF-2) Transactions, Revision 1.0 - Final Text, August 12, 2011" available at http://www.ihe.net/technical_framework/upload/ihe_pcd_tf_vol2_ft_2011-08-12.pdf says
..The common static definition of the HL7 acknowledgement (ACK) message is described in Appendix G, "HL7 Implementation Notes"..
which says
G.1 Network Guidelines
The HL7 2.6 standard does not define a network communications protocol. Beginning with HL7 2.2, the definitions of lower layer protocols were moved to the Implementation Guide, but are not HL7 requirements. The IHE Framework makes these recommendations:
Applications shall use the Minimal Lower Layer Protocol defined in Appendix C of the HL7 Implementation Guide.
An application that wants to send a message (initiate a transaction) will initiate a network connection to start the transaction. The receiver application will respond with an acknowledgement or response to query but will not initiate new transactions on this network connection
G.1.1 Acknowledgment Modes
ACKNOWLEDGMENT MESSAGES
Acknowledgment messages may be defined on an application basis. However the simple general acknowledgment message (ACK) may be used where the application does not define a special message (application level acknowledgment) and in other cases as described in Section 2.9, "Message Processing Rules".
The IHE PCD transaction PCD-03 supports „enhanced mode‟ acknowledgements. See discussion under PCD-03 Transactions as well as in B.1 MSH – Message Header Segment and B.2 MSA – Message Acknowledgement Segment
and document "Health Level Seven, Version 2.6 © 2007, Chapter 2: Control" coming from the "HL7 Messaging Standard Version 2.6" package which can be downloaded from http://www.hl7.org/implement/standards/product_brief.cfm?product_id=185 describes the accept and validate behavior in
2.9.2 Message response using the original processing rules
..too long to quote..
2.9.3 Response using enhanced acknowledgement
..too long to quote..
depending on the values of MSH-15 Accept Acknowledgement Type and MSH-16 Application Acknowledgment Type fields in the HL7 message
The above chapters from the HL7 standard contain what you want to read and implement/support.
EDIT:
Simply put, in HL7 protocol in every message sent the sender may request an ACK receipt by flagging appropriate fields in the message header segment. IHE does not remove this rule and does not enforce any other but enables any other convention to be defined on an application basis. Correct expected behavior is defined by the HL7 specification and in order to get it right and create a conforming implementation (without hidden surprises for your 3rd parties) you may need to read it several times (see also Stack Overflow: How can I make my system HL7 certified?)
For example this is how HAPI library handles the ACKing, snippet comes from http://sourceforge.net/p/hl7api/code/764/tree/tags/Root_REL_1_2/hapi-mvn/hapi-base/src/main/java/ca/uhn/hl7v2/protocol/impl/ProcessorImpl.java
/**
* #see ca.uhn.hl7v2.protocol.Processor#cycle(boolean)
*/
public void cycle(boolean expectingAck) throws HL7Exception {
log.debug("In cycle({})", expectingAck);
cleanReservations();
cleanAcceptAcks();
cleanReservedMessages();
Transportable in = null;
try {
if (expectingAck) {
in = tryReceive(myContext.getLocallyDrivenTransportLayer());
} else {
in = tryReceive(myContext.getRemotelyDrivenTransportLayer());
}
} catch (TransportException e) {
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {}
throw e;
}
// log
if (in != null) {
log.debug("Received message: {}", in.getMessage());
} else {
log.debug("Received no message");
}
// If we have a message, handle it
if (in != null) {
String acceptAckNeeded = null;
// String appAckNeeded = null;
String ackCode = null;
String ackId = null;
try {
String[] fieldPaths = {"MSH-15", "MSH-16", "MSA-1", "MSA-2"};
String[] fields = PreParser.getFields(in.getMessage(), fieldPaths);
acceptAckNeeded = fields[0];
// appAckNeeded = fields[1];
ackCode = fields[2];
ackId = fields[3];
} catch (HL7Exception e) {
log.warn("Failed to parse accept ack fields in incoming message", e);
}
if (ackId != null && ackCode != null && ackCode.startsWith("C")) {
long expiryTime = System.currentTimeMillis() + 1000 * 60;
myAcceptAcks.put(ackId, new ExpiringTransportable(in, expiryTime));
} else {
AcceptAcknowledger.AcceptACK ack = AcceptAcknowledger.validate(getContext(), in);
if ((acceptAckNeeded != null && acceptAckNeeded.equals(AL))
|| (acceptAckNeeded != null && acceptAckNeeded.equals(ER) && !ack.isAcceptable())
|| (acceptAckNeeded != null && acceptAckNeeded.equals(SU) && ack.isAcceptable())) {
trySend(myContext.getRemotelyDrivenTransportLayer(), ack.getMessage());
}
if (ack.isAcceptable()) {
if (isReserved(ackId)) {
log.debug("Received expected ACK message with ACK ID: {}", ackId);
removeReservation(ackId);
long expiryTime = System.currentTimeMillis() + 1000 * 60 * 5;
myAvailableMessages.put(ackId, new ExpiringTransportable(in, expiryTime));
} else {
log.debug("Sending message to router");
Transportable out = myContext.getRouter().processMessage(in);
sendAppResponse(out);
}
} else {
// TODO: should we do something more here? Might be nice to
// allow a configurable handler for this situation
log.warn("Incoming message was not acceptable");
}
}
} else {
String transport = expectingAck ? " Locally driven " : "Remotely driven";
log.debug("{} TransportLayer.receive() returned null.", transport);
}
sleepIfNeeded();
log.debug("Exiting cycle()");
}
Thanks for your answer :)
of course that it is better to use an ACK to make sure if the receiver is getting the message but what I wanted to know if it was mandatory or not using the PCD-01 transaction.
I've read your documents and what I've understood is that the use of ACK depends on the MSH-15 and MSH-16 fields content, but with the following information:
An application that wants to send a message (initiate a transaction) will initiate a network connection to start the transaction. The receiver application will respond with an acknowledgement or response to query but will not initiate new transactions on this network connection
I understand that the ACK is only at the beginning of the connection not after every message, is it right?
I have to develop an Android application using phongap that retrieves the sensors data from the device.
One of the sensors I have to listen to is the ambient light sensor. This sensor has no implementation in phoneGap, so I have to add it as a plugin to PhoneGap.
I know how to add plugin and I know how to access the ALS data from Java - but in order to be sure that I am implementing it well I want to implement it as PhoneGap implements other sensors like Accelerometer. Therefor I wrote a ALSManager class in java that I implemented as I found the Accelerometer was implemented here:
https://github.com/apache/cordova-android/blob/master/framework/src/org/apache/cordova/AccelListener.java
and added lightSensor and lightValues modules like the acceleromter and acceleration modules.
But when I run this application I got following error message:
TypeError: Object # has no method 'getCurrentLight'
(and in the lightSensor module I have getCurrentLight method).
does any one can please suggest me what I am missing? or what do I have to do?
Thanks in advance,
The code I added in the cordova-2.5.0.js. Let me know if it's not enough:
// file: lib/common/plugin/LightValues.js
define("cordova/plugin/LightValues", function(require, exports, module) {
var Acceleration = function(lux, timestamp) {
this.lux = lux;
this.timestamp = timestamp || (new Date()).getTime();
};
module.exports = LightValues;
});
// file: lib/common/plugin/lightSensor.js
define("cordova/plugin/lightSensor", function(require, exports, module) {
/**
* This class provides access to device accelerometer data.
* #constructor
*/
var argscheck = require('cordova/argscheck'),
utils = require("cordova/utils"),
exec = require("cordova/exec"),
LightValues = require('cordova/plugin/LightValues');
// Is the accel sensor running?
var running = false;
// Keeps reference to watchAcceleration calls.
var timers = {};
// Array of listeners; used to keep track of when we should call start and stop.
var listeners = [];
// Last returned acceleration object from native
var light = null;
// Tells native to start.
function start() {
exec(function(a) {
var tempListeners = listeners.slice(0);
light = new LightValues(a.lux, a.timestamp);
for (var i = 0, l = tempListeners.length; i < l; i++) {
tempListeners[i].win(light);
}
}, function(e) {
var tempListeners = listeners.slice(0);
for (var i = 0, l = tempListeners.length; i < l; i++) {
tempListeners[i].fail(e);
}
}, "Light", "start", []);
running = true;
}
// Tells native to stop.
function stop() {
exec(null, null, "Light", "stop", []);
running = false;
}
// Adds a callback pair to the listeners array
function createCallbackPair(win, fail) {
return {win:win, fail:fail};
}
// Removes a win/fail listener pair from the listeners array
function removeListeners(l) {
var idx = listeners.indexOf(l);
if (idx > -1) {
listeners.splice(idx, 1);
if (listeners.length === 0) {
stop();
}
}
}
var lightSensor = {
/**
* Asynchronously acquires the current acceleration.
*
* #param {Function} successCallback The function to call when the acceleration data is available
* #param {Function} errorCallback The function to call when there is an error getting the acceleration data. (OPTIONAL)
* #param {AccelerationOptions} options The options for getting the accelerometer data such as timeout. (OPTIONAL)
*/
getCurrentLight: function(successCallback, errorCallback, options) {
//argscheck.checkArgs('fFO', 'lightSensor.getCurrentLight', arguments);
var p;
var win = function(a) {
removeListeners(p);
successCallback(a);
};
var fail = function(e) {
removeListeners(p);
errorCallback && errorCallback(e);
};
p = createCallbackPair(win, fail);
listeners.push(p);
if (!running) {
start();
}
},
/**
* Asynchronously acquires the acceleration repeatedly at a given interval.
*
* #param {Function} successCallback The function to call each time the acceleration data is available
* #param {Function} errorCallback The function to call when there is an error getting the acceleration data. (OPTIONAL)
* #param {AccelerationOptions} options The options for getting the accelerometer data such as timeout. (OPTIONAL)
* #return String The watch id that must be passed to #clearWatch to stop watching.
*/
watchLight: function(successCallback, errorCallback, options) {
//argscheck.checkArgs('fFO', 'lightSensor.watchLight', arguments);
// Default interval (10 sec)
var frequency = (options && options.frequency && typeof options.frequency == 'number') ? options.frequency : 10000;
// Keep reference to watch id, and report accel readings as often as defined in frequency
var id = utils.createUUID();
var p = createCallbackPair(function(){}, function(e) {
removeListeners(p);
errorCallback && errorCallback(e);
});
listeners.push(p);
timers[id] = {
timer:window.setInterval(function() {
if (light) {
successCallback(light);
}
}, frequency),
listeners:p
};
if (running) {
// If we're already running then immediately invoke the success callback
// but only if we have retrieved a value, sample code does not check for null ...
if (light) {
successCallback(light);
}
} else {
start();
}
return id;
},
/**
* Clears the specified accelerometer watch.
*
* #param {String} id The id of the watch returned from #watchAcceleration.
*/
clearWatch: function(id) {
// Stop javascript timer & remove from timer list
if (id && timers[id]) {
window.clearInterval(timers[id].timer);
removeListeners(timers[id].listeners);
delete timers[id];
}
}
};
module.exports = lightSensor;
});
I think maybe the problem is that you are adding your plugin code to cordova-2.5.0.js file. Instead what you should do is create a standalone JS file for each of your JavaScript files and then cordova.require() those files in the HTML page where you want to use that functionality.
So, create LightValues.js and LightSensor.js as separate files somewhere in your www folder. Then in your HTML file, make sure to include the JS file: <script type="text/javascript" src="path-to-lightSensor.JS-file"> (You'd only need to include this one file since it require()s the second one.)
Next, in deviceReady() function, you can call the light sensor with var lightSensor = cordova.require("cordova/plugin/lightSensor"). Notice that the cordova/plugin/lightSensor is not the path to the JS file but the name of it module that you declared in the define() section when you wrote the plugin.
After this you should be able to call lightSensor.getCurrentLight(). If you console.log(lightSensor) you would expect to see all of the available methods that you wrote.
Note that I'm not positive that cordova.require and cordova.define work in cordova-2.5. I'd hope that they do but this page sort of suggests it may not be supported until 2.6. If you are having problems after splitting the files out, maybe it is because of this.
I have just developed a light sensor plugin and fortunately succeed. I have just read the code above and found some small mistakes about identifier e.g "var Acceleration = function(lux, timestamp)" the variable should be LightValues instead of Acceleration. So please check the code first to void some essential mistakes. Then, I first use "lux" as the name of the variable but I got an undefined value of light when debugging the program. So I change "lux" into "x" and it did work!
There are 6 files should be paid attention to: the "LightListener.java", "LightValues.js", "LightSensor.js","cordova_plugin.js", "index.html" and "config.xml". If you configure all the 6 files, the program should work.
I have read many questions about Android, J2ME and RecordStore, but I still can't find the answer that could satisfy me.
I need to implement low-level part of my Java app that should work on different platforms, right now this is Android and J2ME, and in future it should work on PC too. I need to store simple data sets, that is almost similar to RecordStore in J2ME:
App should own several record stores with records, each record has:
the id (but it should be "my" id, not auto-returned one as it is in RecordStore),
the data (just a byte array).
I think I should write an Interface with needed methods, and each platform should have its own implementation of this Interface.
But this task seems to be very common (at least, for Android + J2ME), so, maybe there already is some lightweight implementation? I'm asking just because I don't like to re-invent the wheel.
And maybe some suggestions?
So, I wrote interface that does satisfy my requirements, with two implementations: for Android and J2ME.
Here is how does Interface look:
public interface ISDataStore {
/**
* Get number of records in data store.
*/
public int getNumRecords() throws SDataStoreException;
/**
* Get size of one record with specified id in bytes.
*
* #param record_id id of the record
*/
public int getRecordSize(int record_id) throws SDataStoreException;
/**
* Get record.
*
* #param record_id id of the record to read
* #param data byte array where to put the data
* #param offset offset in 'data' array from which should start to copy
*/
public void getRecord(int record_id, byte[] data, int offset) throws SDataStoreException;
/**
* Get record.
*
* #param record_id id of the record to read
*/
public byte[] getRecord(int record_id) throws SDataStoreException;
/**
* Resolves is record with specified id exists or not.
*
* #param record_id id of the record
* #return true if record exists, otherwise false
*/
public boolean isRecordExists(int record_id) throws SDataStoreException;
/**
* Put new record or update existing one.
*
* #param record_id id of the record
* #param data byte array of data
* #param offset offset in the data byte array
* #param length number of bytes to store
*
* #return true if operation was successful, otherwise false
*
*/
public boolean setRecord(int record_id, byte[] data, int offset, int length) throws SDataStoreException;
/**
* Delete the record.
*
* #param record_id id of the record
*
* #return true if operation was successful, otherwise false
*/
public boolean deleteRecord(int record_id) throws SDataStoreException;
/**
* Clear all the records.
*/
public void deleteAll() throws SDataStoreException;
/**
* Close the data store.
*/
public void close() throws SDataStoreException;
}
There is also a factory for data stores:
public interface ISDataStoreFactory {
/**
* #param dataStoreId id of the data store
* #return ISDataStore with given id. Type of this id depends on target platform
*/
public ISDataStore getDataStore(Object dataStoreId) throws SDataStoreException;
/**
* Destroys data store with given id.
* #param dataStoreId id of the data store. Type of this id depends on target platform
*/
public void destroyDataStore(Object dataStoreId) throws SDataStoreException;
}
Docs auto-generated by Doxygen can be found here.
Mercurial repository with Interface and all the implementations can be found here.
How do I use it:
As I already said in my question, I have app for Android and app for J2ME, both these apps does the similar thing. (if anyone interested, they does communication via bluetooth with remote embedded device)
Both apps have common low-level part that does the main job.
I have interface IMainApp, something like that:
public interface IMainApp {
public ISDataStoreFactory getDataStoreFactory();
/*
* ... some other methods
*/
}
Both apps (for Android and for J2ME) have its own implementations of this interface, and they pass its reference to the low-level part. When low-level part wants to open some data store, it uses ISDataStoreFactory returned by IMainApp.getDataStoreFactory. It works just like I want it to work.
Hope it is useful for anyone.
Am shou you what i did
create profiles .. that are values i want to store in the database
Sample profile
public class NewsProfile {
public String uniqid = "", category = "", title = "" ;
public NewsProfile(String uniqid) {
this.uniqid = uniqid;
}
}
Create a News store that accept New Profile
public void saveProfile(int id, NewsProfile profile) {
try {
if (rs != null) {
profile.id = id;
byte[] bytes = toByteArray(profile);
setRecord(profile.id, bytes);
System.err.println("Exists = " + profile.catKey + String.valueOf(profile.status));
}
} catch (Exception e) {
System.err.println("ERROR: saveUpdateProfile" + e.getMessage());
}
}
public NewsProfile getProfileint id) throws RecordStoreException, IOException {
byte[] bytes = rs.getRecord(id);
DataInputStream is = new DataInputStream(new ByteArrayInputStream(bytes));
String uniqid = is.readUTF();
OptionsProfile profile = new NewsProfile (uniqid);
profile.id = id;
profile.catKey = uniqid;
profile.category = is.readUTF();
profile.title = is.readUTF();
return profile;
}
private byte[] toByteArray(NewsProfile profile) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream os = new DataOutputStream(baos);
os.writeUTF(profile.uniqid);
os.writeUTF(profile.category);
os.writeUTF(profile.title);
return baos.toByteArray();
}
What this means is that anytime i want to save data to a database .... what am saving at any point in time is NewsProfile .... You can not implement for different storage you want .. SQLite , RMS , even Web Service
Thanks
:)