]> git.basschouten.com Git - openhab-addons.git/blob
baf5200cd882ff4b62c58f3d70784f2ca23e7fd7
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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 commandAsStringType) {
76                         synchronized (petcareAPI) {
77                             SurePetcarePet pet = petcareAPI.getPet(thing.getUID().getId());
78                             if (pet != null) {
79                                 String newLocationIdStr = commandAsStringType.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 commandAsStringType) {
107                         synchronized (petcareAPI) {
108                             SurePetcarePet pet = petcareAPI.getPet(thing.getUID().getId());
109                             if (pet != null) {
110                                 String commandIdStr = commandAsStringType.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                     if (loc.deviceId != null) {
169                         SurePetcareDevice device = petcareAPI.getDevice(loc.deviceId.toString());
170                         if (device != null) {
171                             updateState(PET_CHANNEL_LOCATION_CHANGED_THROUGH, new StringType(device.name));
172                         }
173                     } else if (loc.userId != null) {
174                         SurePetcareHousehold household = petcareAPI.getHousehold(pet.householdId.toString());
175                         if (household != null) {
176                             Long userId = loc.userId;
177                             household.users.stream().map(user -> user.user).filter(user -> userId.equals(user.userId))
178                                     .forEach(user -> updateState(PET_CHANNEL_LOCATION_CHANGED_THROUGH,
179                                             new StringType(user.userName)));
180                         }
181                     }
182                 }
183                 updateState(PET_CHANNEL_DATE_OF_BIRTH, pet.dateOfBirth == null ? UnDefType.UNDEF
184                         : new DateTimeType(pet.dateOfBirth.atStartOfDay(ZoneId.systemDefault())));
185                 updateState(PET_CHANNEL_WEIGHT,
186                         pet.weight == null ? UnDefType.UNDEF : new QuantityType<Mass>(pet.weight, SIUnits.KILOGRAM));
187                 if (pet.tagId != null) {
188                     SurePetcareTag tag = petcareAPI.getTag(pet.tagId.toString());
189                     if (tag != null) {
190                         updateState(PET_CHANNEL_TAG_IDENTIFIER, new StringType(tag.tag));
191                     }
192                 }
193                 SurePetcarePetFeeding feeding = pet.status.feeding;
194                 if (feeding != null) {
195                     SurePetcareDevice device = petcareAPI.getDevice(feeding.deviceId.toString());
196                     if (device != null) {
197                         updateState(PET_CHANNEL_FEEDER_DEVICE, new StringType(device.name));
198                         int bowlId = device.control.bowls.bowlId;
199                         int numBowls = feeding.feedChange.size();
200                         if (numBowls > 0) {
201                             if (bowlId == BOWL_ID_ONE_BOWL_USED) {
202                                 updateState(PET_CHANNEL_FEEDER_LAST_CHANGE,
203                                         new QuantityType<Mass>(feeding.feedChange.get(0), SIUnits.GRAM));
204                             } else if (bowlId == BOWL_ID_TWO_BOWLS_USED) {
205                                 updateState(PET_CHANNEL_FEEDER_LAST_CHANGE_LEFT,
206                                         new QuantityType<Mass>(feeding.feedChange.get(0), SIUnits.GRAM));
207                                 if (numBowls > 1) {
208                                     updateState(PET_CHANNEL_FEEDER_LAST_CHANGE_RIGHT,
209                                             new QuantityType<Mass>(feeding.feedChange.get(1), SIUnits.GRAM));
210                                 }
211                             }
212                         }
213                         updateState(PET_CHANNEL_FEEDER_LASTFEEDING, new DateTimeType(feeding.feedChangeAt));
214                     }
215                 }
216             } else {
217                 logger.debug("Trying to update unknown pet: {}", thing.getUID().getId());
218             }
219         }
220     }
221
222     /**
223      * Tries to lookup image in cache. If not found, it tries to download the image from its URL.
224      *
225      * @param url the url of the pet photo
226      * @return the pet image as {@link RawType} or UNDEF
227      */
228     private State getPetPhotoFromCache(@Nullable String url) {
229         if (url == null) {
230             return UnDefType.UNDEF;
231         }
232         if (IMAGE_CACHE.containsKey(url)) {
233             try {
234                 byte[] bytes = IMAGE_CACHE.get(url);
235                 String contentType = HttpUtil.guessContentTypeFromData(bytes);
236                 return new RawType(bytes,
237                         contentType == null || contentType.isEmpty() ? RawType.DEFAULT_MIME_TYPE : contentType);
238             } catch (IOException e) {
239                 logger.trace("Failed to download the content of URL '{}'", url, e);
240             }
241         } else {
242             // photo is not yet in cache, download and add
243             RawType image = HttpUtil.downloadImage(url);
244             if (image != null) {
245                 IMAGE_CACHE.put(url, image.getBytes());
246                 return image;
247             }
248         }
249         return UnDefType.UNDEF;
250     }
251 }