001package com.pi4j.io.gpio.trigger;
002
003/*
004 * #%L
005 * **********************************************************************
006 * ORGANIZATION  :  Pi4J
007 * PROJECT       :  Pi4J :: Java Library (Core)
008 * FILENAME      :  IFTTTMakerChannelTrigger.java
009 *
010 * This file is part of the Pi4J project. More information about
011 * this project can be found here:  https://www.pi4j.com/
012 * **********************************************************************
013 * %%
014 * Copyright (C) 2012 - 2021 Pi4J
015 * %%
016 * This program is free software: you can redistribute it and/or modify
017 * it under the terms of the GNU Lesser General Public License as
018 * published by the Free Software Foundation, either version 3 of the
019 * License, or (at your option) any later version.
020 *
021 * This program is distributed in the hope that it will be useful,
022 * but WITHOUT ANY WARRANTY; without even the implied warranty of
023 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
024 * GNU General Lesser Public License for more details.
025 *
026 * You should have received a copy of the GNU General Lesser Public
027 * License along with this program.  If not, see
028 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
029 * #L%
030 */
031
032
033import com.pi4j.io.gpio.GpioPin;
034import com.pi4j.io.gpio.PinState;
035import com.pi4j.io.gpio.event.IFTTTMakerChannelTriggerEvent;
036import com.pi4j.io.gpio.event.IFTTTMakerChannelTriggerListener;
037
038import javax.net.ssl.HttpsURLConnection;
039import java.io.*;
040import java.net.*;
041import java.nio.charset.StandardCharsets;
042import java.text.DateFormat;
043import java.text.SimpleDateFormat;
044import java.util.Date;
045import java.util.List;
046
047@SuppressWarnings("unused")
048public class IFTTTMakerChannelTrigger extends GpioTriggerBase {
049
050    private final String apikey;
051    private final String eventName;
052    private IFTTTMakerChannelTriggerListener listener = null;
053
054    protected static final String IFTTT_MAKER_CHANNEL_URL = "https://maker.ifttt.com/trigger/%s/with/key/%s";
055
056    public IFTTTMakerChannelTrigger(String apikey, String eventName) {
057        super();
058        this.apikey = apikey;
059        this.eventName = eventName;
060    }
061
062    public IFTTTMakerChannelTrigger(String apikey, String eventName, PinState state) {
063        super(state);
064        this.apikey = apikey;
065        this.eventName = eventName;
066    }
067
068    public IFTTTMakerChannelTrigger(String apikey, String eventName, PinState[] states) {
069        super(states);
070        this.apikey = apikey;
071        this.eventName = eventName;
072    }
073
074    public IFTTTMakerChannelTrigger(String apikey, String eventName, List<PinState> states) {
075        super(states);
076        this.apikey = apikey;
077        this.eventName = eventName;
078    }
079
080    public IFTTTMakerChannelTrigger(String apikey, String eventName, IFTTTMakerChannelTriggerListener listener) {
081        super();
082        this.apikey = apikey;
083        this.eventName = eventName;
084        this.listener = listener;
085    }
086
087    public IFTTTMakerChannelTrigger(String apikey, String eventName, PinState state, IFTTTMakerChannelTriggerListener listener) {
088        super(state);
089        this.apikey = apikey;
090        this.eventName = eventName;
091        this.listener = listener;
092    }
093
094    public IFTTTMakerChannelTrigger(String apikey, String eventName, PinState[] states, IFTTTMakerChannelTriggerListener listener) {
095        super(states);
096        this.apikey = apikey;
097        this.eventName = eventName;
098        this.listener = listener;
099    }
100
101    public IFTTTMakerChannelTrigger(String apikey, String eventName, List<PinState> states, IFTTTMakerChannelTriggerListener listener) {
102        super(states);
103        this.apikey = apikey;
104        this.eventName = eventName;
105        this.listener = listener;
106    }
107
108    /**
109     * Use this method to register/assign a custom trigger listener.
110     *
111     * This callback listener is fired when the IFTTTMakerChannelTrigger
112     * receives a GPIO pin change that invokes the trigger.  This listener
113     * provides the consumer an opportunity to be notified of the trigger event,
114     * to optionally abort the triggered event before sending data to the IFTTT
115     * Maker Channel API, or an opportunity to override any data payload values
116     * before transmitting to the IFTTT Maker Channel API.
117     *
118     * @param listener
119     */
120    public void setTriggerListener(IFTTTMakerChannelTriggerListener listener){
121        this.listener = listener;
122    }
123
124    @Override
125    public void invoke(GpioPin pin, PinState state) {
126        try {
127            // create an ISO-8601 complaint date format for timestamp
128            DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
129
130            // create the default JSON payload that will get supplied as the default
131            // data for 'value3' in the IFTTT Maker Channel trigger API
132            String json = "{\"pin\":{\"name\":\"" + pin.getName() +
133                    "\",\"address\":\"" + pin.getPin().getAddress() +
134                    "\",\"provider\":\"" + pin.getPin().getProvider() +
135                    "\",\"mode\":\"" + pin.getMode().getName() +
136                    "\",\"direction\":\"" + pin.getMode().getDirection() +
137                    "\",\"pull\":\"" + pin.getPullResistance().getName() +
138                    "\"},\"state\":{" +
139                    "\"name\":\"" + state.getName() +
140                    "\",\"value\":\"" + state.getValue() +
141                    "\",\"is-high\":\"" + state.isHigh() +
142                    "\",\"is-low\":\"" + state.isLow() +
143                    "\"},\"timestamp\":\"" + df.format(new Date()) + "\"}";
144
145            // create an IFTTT Maker Channel event
146            IFTTTMakerChannelTriggerEvent event = new IFTTTMakerChannelTriggerEvent(
147                    this,                               // IFTTTMakerChannelTrigger instance
148                    pin,                                // GPIO PIN instance
149                    state,                              // GPIO PIN STATE
150                    eventName,                          // IFTTT EVENT NAME
151                    pin.getName(),                      // VALUE 1
152                    Integer.toString(state.getValue()), // VALUE 2
153                    json);                              // VALUE 3
154
155            // if the consumer configured a custom listener for callback events,
156            // then we need to invoke the listner instance 'onTriggered' callback
157            // method with the IFTTTMakerChannelTriggerEvent instance.
158            if(this.listener != null){
159                if(this.listener.onTriggered(event) == false) {
160                    // abort
161                    return;
162                }
163            }
164
165            // we must URL encode the IFTTT payload data values (value1, value2, value3)
166            String value1 = URLEncoder.encode(event.getValue1(), StandardCharsets.UTF_8.name());
167            String value2 = URLEncoder.encode(event.getValue2(), StandardCharsets.UTF_8.name());
168            String value3 = URLEncoder.encode(event.getValue3(), StandardCharsets.UTF_8.name());
169
170            // create the URL parameters and post data
171            String urlParameters  = "value1=" + value1 + "&value2=" + value2 + "&value3=" + value3;
172            byte[] postData       = urlParameters.getBytes(StandardCharsets.UTF_8);
173
174            // create the IFTTT Maker Channel trigger URL & connection
175            URL url = new URL(String.format(IFTTT_MAKER_CHANNEL_URL, eventName, apikey));
176            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
177            connection.setRequestMethod("POST");
178            connection.setDoOutput(true);
179            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
180            connection.setRequestProperty("charset", "utf-8");
181            connection.setRequestProperty("Content-Length", Integer.toString(postData.length));
182            connection.setUseCaches(false);
183            try( DataOutputStream stream = new DataOutputStream(connection.getOutputStream())) {
184                stream.write( postData );
185            }
186
187            // get HTTP response
188            int responseCode = connection.getResponseCode();
189
190            // if the HTTP post resulted in an error, then raise an exception
191            if(responseCode != 200) {
192                System.err.println("IFTTT MakerChannel ERROR Response Code: " + responseCode);
193                BufferedReader in = new BufferedReader( new InputStreamReader(connection.getInputStream()));
194                String inputLine;
195                StringBuffer response = new StringBuffer();
196                while ((inputLine = in.readLine()) != null) {
197                    response.append(inputLine);
198                }
199                in.close();
200                throw new RuntimeException(response.toString());
201            }
202        }
203        catch (IOException e) {
204            throw new RuntimeException(e);
205        }
206    }
207}