/* This file is part of Wattzap Community Edition. * * Wattzap Community Edtion is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Wattzap Community Edition is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Wattzap. If not, see <http://www.gnu.org/licenses/>. */ package com.wattzap.model.ant; import java.util.HashMap; import java.util.concurrent.TimeUnit; import java.util.logging.ConsoleHandler; import java.util.logging.Level; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.cowboycoders.ant.Channel; import org.cowboycoders.ant.NetworkKey; import org.cowboycoders.ant.Node; import org.cowboycoders.ant.events.MessageCondition; import org.cowboycoders.ant.events.MessageConditionFactory; import org.cowboycoders.ant.interfaces.AntCommunicationException; import org.cowboycoders.ant.interfaces.AntTransceiver; import org.cowboycoders.ant.messages.ChannelType; import org.cowboycoders.ant.messages.SlaveChannelType; import org.cowboycoders.ant.messages.commands.ChannelRequestMessage; import org.cowboycoders.ant.messages.commands.ChannelRequestMessage.Request; import org.cowboycoders.ant.messages.data.BroadcastDataMessage; import org.cowboycoders.ant.messages.responses.ChannelIdResponse; import com.wattzap.controller.MessageBus; import com.wattzap.controller.MessageCallback; import com.wattzap.controller.Messages; import com.wattzap.model.UserPreferences; /** * Gets data from Ant device and calculates speed, distance, cadence etc. * * @author David George * @date 30 May 2013 * * (c) 2013, 2014 David George / Wattzap.com */ public class Ant implements MessageCallback { private Node node = null; private NetworkKey key = null; private HashMap<String,AntListener> antListeners; private HashMap<String,Channel> antChannels; private UserPreferences userPrefs = UserPreferences.INSTANCE; private static final int ANT_SPORT_FREQ = 57; // 0x39 private static Logger logger = LogManager.getLogger("Ant"); /* * This should match the device you are connecting with. Some devices are * put into pairing mode (which sets this bit). * * Note: Many ANT+ sport devices do not set this bit (eg. HRM strap). * * See ANT+ docs. */ private static final boolean PAIRING_FLAG = false; /* * Should match device transmission id (0-255). Special rules apply for * shared channels. See ANT+ protocol. * * 0: wildcard, matches any value (slave only) */ private static final int TRANSMISSION_TYPE = 0/*1*/; public static final Level LOG_LEVEL = Level.SEVERE; public Ant(HashMap<String,AntListener> listeners) { antListeners = listeners; AntTransceiver antchip = null; try { antchip = new AntTransceiver(0); } catch (AntCommunicationException e) { antchip = new AntTransceiver(0, AntTransceiver.ANTUSBM_ID); } // initialises node with chosen driver node = new Node(antchip); // ANT+ key key = new NetworkKey(0xB9, 0xA5, 0x21, 0xFB, 0xBD, 0x72, 0xC3, 0x45); key.setName("N:ANT+"); } public Ant(AntListener scListener, AntListener hrmListener) { // optional: enable console logging with Level = LOG_LEVEL setupLogging(); /* * Choose driver: AndroidAntTransceiver or AntTransceiver * * AntTransceiver(int deviceNumber) deviceNumber : 0 ... number of usb * sticks plugged in 0: first usb ant-stick */ //SCListener = scListener; //HRListener = hrmListener; AntTransceiver antchip = null; try { antchip = new AntTransceiver(0); } catch (AntCommunicationException e) { antchip = new AntTransceiver(0, AntTransceiver.ANTUSBM_ID); } // initialises node with chosen driver node = new Node(antchip); // ANT+ key key = new NetworkKey(0xB9, 0xA5, 0x21, 0xFB, 0xBD, 0x72, 0xC3, 0x45); key.setName("N:ANT+"); } public void register() { MessageBus.INSTANCE.register(Messages.START, this); MessageBus.INSTANCE.register(Messages.STOP, this); } public static void setupLogging() { // set logging level AntTransceiver.LOGGER.setLevel(LOG_LEVEL); ConsoleHandler handler = new ConsoleHandler(); // PUBLISH this level handler.setLevel(LOG_LEVEL); AntTransceiver.LOGGER.addHandler(handler); } public static void printChannelConfig(Channel channel) { // build request ChannelRequestMessage msg = new ChannelRequestMessage( channel.getNumber(), Request.CHANNEL_ID); // response should be an instance of ChannelIdResponse MessageCondition condition = MessageConditionFactory .newInstanceOfCondition(ChannelIdResponse.class); try { // send request (blocks until reply received or timeout expired) ChannelIdResponse response = (ChannelIdResponse) channel .sendAndWaitForMessage(msg, condition, 5L, TimeUnit.SECONDS, null); /* * System.out.println(); * System.out.println("Device configuration: "); * System.out.println("deviceID: " + response.getDeviceNumber()); * System.out.println("deviceType: " + response.getDeviceType()); * System.out.println("transmissionType: " + * response.getTransmissionType()); * System.out.println("pairing flag set: " + * response.isPairingFlagSet()); System.out.println(); */ } catch (Exception e) { logger.error(e.getLocalizedMessage()); } } private static int getChannelId(Channel channel) { // build request ChannelRequestMessage msg = new ChannelRequestMessage( channel.getNumber(), Request.CHANNEL_ID); // response should be an instance of ChannelIdResponse MessageCondition condition = MessageConditionFactory .newInstanceOfCondition(ChannelIdResponse.class); try { // send request (blocks until reply received or timeout expired) ChannelIdResponse response = (ChannelIdResponse) channel .sendAndWaitForMessage(msg, condition, 5L, TimeUnit.SECONDS, null); return response.getDeviceNumber(); } catch (Exception e) { logger.error(e.getLocalizedMessage()); } return 0; } public void close() { // stop listening for (Channel channel : antChannels.values()) { if (!channel.isFree()) { logger.debug("Stopping ANT device"); channel.close(); // resets channel configuration channel.unassign(); // return the channel to the pool of available channels node.freeChannel(channel); } } // cleans up : gives up control of usb device etc. node.stop(); } public void open() { /* must be called before any configuration takes place */ node.start(); /* sends reset request : resets channels to default state */ node.reset(); // specs say wait 500ms after reset before sending any more host // commands try { Thread.sleep(500); } catch (InterruptedException ex) { } // sets network key of network zero node.setNetworkKey(0, key); antChannels = new HashMap<String,Channel>(); for (AntListener listener : antListeners.values()) { Channel channel = node.getFreeChannel(); antChannels.put(listener.getName(), channel); // Arbitrary name : useful for identifying channel channel.setName(listener.getName()); System.out.println("opening " + listener.getName()); // choose slave or master type. Constructors exist to set // two-way/one-way and shared/non-shared variants. ChannelType channelType = new SlaveChannelType(); // use ant network key "N:ANT+" channel.assign("N:ANT+", channelType); // registers our Heart Rate and Speed and Cadence callbacks with the // channel channel.registerRxListener(listener, BroadcastDataMessage.class); // ******* start device specific configuration ****** channel.setId(listener.getChannelId(), listener.getDeviceType(), TRANSMISSION_TYPE, PAIRING_FLAG); channel.setFrequency(ANT_SPORT_FREQ); channel.setPeriod(listener.getChannelPeriod()); // ******* end device specific configuration ****** // timeout before we give up looking for device channel.setSearchTimeout(Channel.SEARCH_TIMEOUT_NEVER); // start listening channel.open(); } } // public void open() { // /* must be called before any configuration takes place */ // node.start(); // // /* sends reset request : resets channels to default state */ // node.reset(); // // // specs say wait 500ms after reset before sending any more host // // commands // try { // Thread.sleep(500); // } catch (InterruptedException ex) { // // } // // // sets network key of network zero // node.setNetworkKey(0, key); // // scChannel = node.getFreeChannel(); // // // Arbitrary name : useful for identifying channel // scChannel.setName(SCListener.getName()); // // System.out.println("opening " + SCListener.getName()); // // // choose slave or master type. Constructors exist to set // // two-way/one-way and shared/non-shared variants. // ChannelType channelType = new SlaveChannelType(); // // // use ant network key "N:ANT+" // scChannel.assign("N:ANT+", channelType); // // // registers our Heart Rate and Speed and Cadence callbacks with the // // channel // scChannel.registerRxListener(SCListener, BroadcastDataMessage.class); // // // ******* start device specific configuration ****** // scChannel.setId(SCListener.getChannelId(), SCListener.getDeviceType(), // TRANSMISSION_TYPE, PAIRING_FLAG); // // scChannel.setFrequency(ANT_SPORT_FREQ); // scChannel.setPeriod(SCListener.getChannelPeriod()); // // // ******* end device specific configuration ****** // // // timeout before we give up looking for device // scChannel.setSearchTimeout(Channel.SEARCH_TIMEOUT_NEVER); // // // start listening // scChannel.open(); // // // *** Heart Rate Monitor *** // // hrChannel = node.getFreeChannel(); // // // Arbitrary name : useful for identifying channel // hrChannel.setName(HRListener.getName()); // // ChannelType channelType2 = new SlaveChannelType(); // use ant // // network key // // "N:ANT+" // hrChannel.assign("N:ANT+", channelType2); // // // registers an instance of our callback with the channel // hrChannel.registerRxListener(HRListener, BroadcastDataMessage.class); // // // start device specific configuration // // hrChannel.setId(HRListener.getChannelId(), HRListener.getDeviceType(), // TRANSMISSION_TYPE, PAIRING_FLAG); // // hrChannel.setFrequency(ANT_SPORT_FREQ); // // hrChannel.setPeriod(HRListener.getChannelPeriod()); // // // ******* end device specific configuration // // // timeout before we give up looking for device // hrChannel.setSearchTimeout(Channel.SEARCH_TIMEOUT_NEVER); // // // start listening // hrChannel.open(); // } @Override public void callback(Messages message, Object o) { switch (message) { case STOP: close(); break; case START: open(); break; } } public int getChannelId(String name) { Channel channel = antChannels.get(name); return getChannelId(channel); } }