]> git.basschouten.com Git - openhab-addons.git/blob
ec1fa5b67f01c30123e58c963a1fdac563d913d3
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.surepetcare.internal.handler;
14
15 import static org.openhab.binding.surepetcare.internal.SurePetcareConstants.*;
16
17 import java.io.IOException;
18 import java.time.ZoneId;
19 import java.time.ZonedDateTime;
20
21 import javax.measure.quantity.Mass;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.surepetcare.internal.SurePetcareAPIHelper;
26 import org.openhab.binding.surepetcare.internal.SurePetcareApiException;
27 import org.openhab.binding.surepetcare.internal.dto.SurePetcareDevice;
28 import org.openhab.binding.surepetcare.internal.dto.SurePetcareHousehold;
29 import org.openhab.binding.surepetcare.internal.dto.SurePetcarePet;
30 import org.openhab.binding.surepetcare.internal.dto.SurePetcarePetActivity;
31 import org.openhab.binding.surepetcare.internal.dto.SurePetcarePetFeeding;
32 import org.openhab.binding.surepetcare.internal.dto.SurePetcareTag;
33 import org.openhab.core.cache.ByteArrayFileCache;
34 import org.openhab.core.io.net.http.HttpUtil;
35 import org.openhab.core.library.types.DateTimeType;
36 import org.openhab.core.library.types.DecimalType;
37 import org.openhab.core.library.types.QuantityType;
38 import org.openhab.core.library.types.RawType;
39 import org.openhab.core.library.types.StringType;
40 import org.openhab.core.library.unit.SIUnits;
41 import org.openhab.core.thing.ChannelUID;
42 import org.openhab.core.thing.Thing;
43 import org.openhab.core.types.Command;
44 import org.openhab.core.types.RefreshType;
45 import org.openhab.core.types.State;
46 import org.openhab.core.types.UnDefType;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49
50 /**
51  * The {@link SurePetcarePetHandler} is responsible for handling the things created to represent Sure Petcare pets.
52  *
53  * @author Rene Scherer - Initial Contribution
54  * @author Holger Eisold - Added pet feeder status, location time offset
55  */
56 @NonNullByDefault
57 public class SurePetcarePetHandler extends SurePetcareBaseObjectHandler {
58
59     private final Logger logger = LoggerFactory.getLogger(SurePetcarePetHandler.class);
60
61     private static final ByteArrayFileCache IMAGE_CACHE = new ByteArrayFileCache("org.openhab.binding.surepetcare");
62
63     public SurePetcarePetHandler(Thing thing, SurePetcareAPIHelper petcareAPI) {
64         super(thing, petcareAPI);
65     }
66
67     @Override
68     public void handleCommand(ChannelUID channelUID, Command command) {
69         if (command instanceof RefreshType) {
70             updateThingCache.getValue();
71         } else {
72             switch (channelUID.getId()) {
73                 case PET_CHANNEL_LOCATION:
74                     logger.debug("Received location update command: {}", command.toString());
75                     if (command instanceof StringType) {
76                         synchronized (petcareAPI) {
77                             SurePetcarePet pet = petcareAPI.getPet(thing.getUID().getId());
78                             if (pet != null) {
79                                 String newLocationIdStr = ((StringType) command).toString();
80                                 try {
81                                     Integer newLocationId = Integer.valueOf(newLocationIdStr);
82                                     // Only update if location has changed. (Needed for Group:Switch item)
83                                     if ((pet.status.activity.where.equals(newLocationId)) || newLocationId.equals(0)) {
84                                         logger.debug("Location has not changed, skip pet id: {} with loc id: {}",
85                                                 pet.id, newLocationId);
86                                     } else {
87                                         logger.debug("Received new location: {}", newLocationId);
88                                         petcareAPI.setPetLocation(pet, newLocationId, ZonedDateTime.now());
89                                         updateState(PET_CHANNEL_LOCATION,
90                                                 new StringType(pet.status.activity.where.toString()));
91                                         updateState(PET_CHANNEL_LOCATION_CHANGED,
92                                                 new DateTimeType(pet.status.activity.since));
93                                     }
94                                 } catch (NumberFormatException e) {
95                                     logger.warn("Invalid location id: {}, ignoring command", newLocationIdStr, e);
96                                 } catch (SurePetcareApiException e) {
97                                     logger.warn("Error from SurePetcare API. Can't update location {} for pet {}",
98                                             newLocationIdStr, pet, e);
99                                 }
100                             }
101                         }
102                     }
103                     break;
104                 case PET_CHANNEL_LOCATION_TIMEOFFSET:
105                     logger.debug("Received location time offset update command: {}", command.toString());
106                     if (command instanceof StringType) {
107                         synchronized (petcareAPI) {
108                             SurePetcarePet pet = petcareAPI.getPet(thing.getUID().getId());
109                             if (pet != null) {
110                                 String commandIdStr = ((StringType) command).toString();
111                                 try {
112                                     Integer commandId = Integer.valueOf(commandIdStr);
113                                     Integer currentLocation = pet.status.activity.where;
114                                     logger.debug("Received new location: {}", currentLocation == 1 ? 2 : 1);
115                                     // We set the location to the opposite state.
116                                     // We also set location to INSIDE (1) if currentLocation is Unknown (0)
117                                     if (commandId == 10 || commandId == 30 || commandId == 60) {
118                                         ZonedDateTime time = ZonedDateTime.now().minusMinutes(commandId);
119                                         petcareAPI.setPetLocation(pet, currentLocation == 1 ? 2 : 1, time);
120
121                                     }
122                                     updateState(PET_CHANNEL_LOCATION,
123                                             new StringType(pet.status.activity.where.toString()));
124                                     updateState(PET_CHANNEL_LOCATION_CHANGED,
125                                             new DateTimeType(pet.status.activity.since));
126                                     updateState(PET_CHANNEL_LOCATION_TIMEOFFSET, UnDefType.UNDEF);
127                                 } catch (NumberFormatException e) {
128                                     logger.warn("Invalid location id: {}, ignoring command", commandIdStr, e);
129                                 } catch (SurePetcareApiException e) {
130                                     logger.warn("Error from SurePetcare API. Can't update location {} for pet {}",
131                                             commandIdStr, pet, e);
132                                 }
133                             }
134                         }
135                     }
136                     break;
137                 default:
138                     logger.warn("Update on unsupported channel {}", channelUID.getId());
139             }
140         }
141     }
142
143     @Override
144     protected void updateThing() {
145         synchronized (petcareAPI) {
146             SurePetcarePet pet = petcareAPI.getPet(thing.getUID().getId());
147             if (pet != null) {
148                 logger.debug("Updating all thing channels for pet : {}", pet);
149                 updateState(PET_CHANNEL_ID, new DecimalType(pet.id));
150                 updateState(PET_CHANNEL_NAME, pet.name == null ? UnDefType.UNDEF : new StringType(pet.name));
151                 updateState(PET_CHANNEL_COMMENT, pet.comments == null ? UnDefType.UNDEF : new StringType(pet.comments));
152                 updateState(PET_CHANNEL_GENDER,
153                         pet.genderId == null ? UnDefType.UNDEF : new StringType(pet.genderId.toString()));
154                 updateState(PET_CHANNEL_BREED,
155                         pet.breedId == null ? UnDefType.UNDEF : new StringType(pet.breedId.toString()));
156                 updateState(PET_CHANNEL_SPECIES,
157                         pet.speciesId == null ? UnDefType.UNDEF : new StringType(pet.speciesId.toString()));
158                 updateState(PET_CHANNEL_PHOTO,
159                         pet.photo == null ? UnDefType.UNDEF : getPetPhotoFromCache(pet.photo.location));
160
161                 SurePetcarePetActivity loc = pet.status.activity;
162                 if (loc != null) {
163                     updateState(PET_CHANNEL_LOCATION, new StringType(loc.where.toString()));
164                     if (loc.since != null) {
165                         updateState(PET_CHANNEL_LOCATION_CHANGED, new DateTimeType(loc.since));
166                     }
167                 }
168                 updateState(PET_CHANNEL_DATE_OF_BIRTH, pet.dateOfBirth == null ? UnDefType.UNDEF
169                         : new DateTimeType(pet.dateOfBirth.atStartOfDay(ZoneId.systemDefault())));
170                 updateState(PET_CHANNEL_WEIGHT,
171                         pet.weight == null ? UnDefType.UNDEF : new QuantityType<Mass>(pet.weight, SIUnits.KILOGRAM));
172                 if (pet.tagId != null) {
173                     SurePetcareTag tag = petcareAPI.getTag(pet.tagId.toString());
174                     if (tag != null) {
175                         updateState(PET_CHANNEL_TAG_IDENTIFIER, new StringType(tag.tag));
176                     }
177                 }
178                 if (pet.status.activity.deviceId != null) {
179                     SurePetcareDevice device = petcareAPI.getDevice(pet.status.activity.deviceId.toString());
180                     if (device != null) {
181                         updateState(PET_CHANNEL_LOCATION_CHANGED_THROUGH, new StringType(device.name));
182                     }
183                 } else if (pet.status.activity.userId != null) {
184                     SurePetcareHousehold household = petcareAPI.getHousehold(pet.householdId.toString());
185                     if (household != null) {
186                         Long userId = pet.status.activity.userId;
187                         household.users.stream().map(user -> user.user).filter(user -> userId.equals(user.userId))
188                                 .forEach(user -> updateState(PET_CHANNEL_LOCATION_CHANGED_THROUGH,
189                                         new StringType(user.userName)));
190                     }
191                 }
192                 SurePetcarePetFeeding feeding = pet.status.feeding;
193                 if (feeding != null) {
194                     SurePetcareDevice device = petcareAPI.getDevice(feeding.deviceId.toString());
195                     if (device != null) {
196                         updateState(PET_CHANNEL_FEEDER_DEVICE, new StringType(device.name));
197                         int bowlId = device.control.bowls.bowlId;
198                         int numBowls = feeding.feedChange.size();
199                         if (numBowls > 0) {
200                             if (bowlId == BOWL_ID_ONE_BOWL_USED) {
201                                 updateState(PET_CHANNEL_FEEDER_LAST_CHANGE,
202                                         new QuantityType<Mass>(feeding.feedChange.get(0), SIUnits.GRAM));
203                             } else if (bowlId == BOWL_ID_TWO_BOWLS_USED) {
204                                 updateState(PET_CHANNEL_FEEDER_LAST_CHANGE_LEFT,
205                                         new QuantityType<Mass>(feeding.feedChange.get(0), SIUnits.GRAM));
206                                 if (numBowls > 1) {
207                                     updateState(PET_CHANNEL_FEEDER_LAST_CHANGE_RIGHT,
208                                             new QuantityType<Mass>(feeding.feedChange.get(1), SIUnits.GRAM));
209                                 }
210                             }
211                         }
212                         updateState(PET_CHANNEL_FEEDER_LASTFEEDING, new DateTimeType(feeding.feedChangeAt));
213                     }
214                 }
215             } else {
216                 logger.debug("Trying to update unknown pet: {}", thing.getUID().getId());
217             }
218         }
219     }
220
221     /**
222      * Tries to lookup image in cache. If not found, it tries to download the image from its URL.
223      *
224      * @param url the url of the pet photo
225      * @return the pet image as {@link RawType} or UNDEF
226      */
227     private State getPetPhotoFromCache(@Nullable String url) {
228         if (url == null) {
229             return UnDefType.UNDEF;
230         }
231         if (IMAGE_CACHE.containsKey(url)) {
232             try {
233                 byte[] bytes = IMAGE_CACHE.get(url);
234                 String contentType = HttpUtil.guessContentTypeFromData(bytes);
235                 return new RawType(bytes,
236                         contentType == null || contentType.isEmpty() ? RawType.DEFAULT_MIME_TYPE : contentType);
237             } catch (IOException e) {
238                 logger.trace("Failed to download the content of URL '{}'", url, e);
239             }
240         } else {
241             // photo is not yet in cache, download and add
242             RawType image = HttpUtil.downloadImage(url);
243             if (image != null) {
244                 IMAGE_CACHE.put(url, image.getBytes());
245                 return image;
246             }
247         }
248         return UnDefType.UNDEF;
249     }
250 }