diff --git a/pom.xml b/pom.xml index 32be76c..d72ab9b 100644 --- a/pom.xml +++ b/pom.xml @@ -27,6 +27,8 @@ saic-java-client saic-java-api-gateway saic-java-api-cli + saic-java-rest-api + saic-java-rest-client saic-java-mqtt-gateway diff --git a/saic-java-rest-api/pom.xml b/saic-java-rest-api/pom.xml new file mode 100644 index 0000000..7c32ae3 --- /dev/null +++ b/saic-java-rest-api/pom.xml @@ -0,0 +1,15 @@ + + + 4.0.0 + + + io.github.saic-ismart-api + saic-ismart-api-parent + 0.0.0-SNAPSHOT + + + saic-java-rest-api + SAIC Java REST API + Implementation of the SAIC Rest API in Java + + diff --git a/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/AESUtils.java b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/AESUtils.java new file mode 100644 index 0000000..1c85574 --- /dev/null +++ b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/AESUtils.java @@ -0,0 +1,45 @@ +package net.heberling.ismart.java.rest; + +import java.nio.charset.StandardCharsets; +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +public class AESUtils { + + public static final String AES = "AES"; + + public static String decrypt(String cipherText, String hexKey, String hexIV) { + if (StringUtils.isEmpty(cipherText) + || StringUtils.isEmpty(hexKey) + || StringUtils.isEmpty(hexIV)) { + return null; + } + try { + SecretKeySpec secretKeySpec = new SecretKeySpec(HexUtils.hexToBytes(hexKey), AES); + IvParameterSpec ivParameterSpec = new IvParameterSpec(HexUtils.hexToBytes(hexIV)); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(2, secretKeySpec, ivParameterSpec); + return new String(cipher.doFinal(HexUtils.hexToBytes(cipherText)), StandardCharsets.UTF_8); + } catch (Exception e2) { + throw new RuntimeException(e2); + } + } + + public static String encrypt(String plainText, String hexKey, String hexIV) { + if (StringUtils.isEmpty(plainText) + || StringUtils.isEmpty(hexKey) + || StringUtils.isEmpty(hexIV)) { + return null; + } + try { + SecretKeySpec secretKeySpec = new SecretKeySpec(HexUtils.hexToBytes(hexKey), AES); + IvParameterSpec ivParameterSpec = new IvParameterSpec(HexUtils.hexToBytes(hexIV)); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(1, secretKeySpec, ivParameterSpec); + return HexUtils.bytesToHex(cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8))); + } catch (Exception e2) { + throw new RuntimeException(e2); + } + } +} diff --git a/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/APIConfig.java b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/APIConfig.java new file mode 100644 index 0000000..40423d3 --- /dev/null +++ b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/APIConfig.java @@ -0,0 +1,8 @@ +package net.heberling.ismart.java.rest; + +public final class APIConfig { + public static final String CONTENT_ENCRYPTED = "1"; + public static final String PARAM_AUTHENTICATION = "Basic c3dvcmQ6c3dvcmRfc2VjcmV0"; + public static final String TENANT_ID = "459771"; + public static final String USER_TYPE = "app"; +} diff --git a/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/EncryptionUtils.java b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/EncryptionUtils.java new file mode 100644 index 0000000..c98660e --- /dev/null +++ b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/EncryptionUtils.java @@ -0,0 +1,105 @@ +package net.heberling.ismart.java.rest; + +public class EncryptionUtils { + private EncryptionUtils() {} + + public static String calculateRequestVerification( + String resourcePath, + long sendDate, + String tenant, + String contentType, + String bodyEncrypted, + String token) { + String str9 = resourcePath + tenant + token + APIConfig.USER_TYPE; + String a2 = HashUtils.md5(str9); + String str10 = sendDate + APIConfig.CONTENT_ENCRYPTED + contentType; + String a3 = HashUtils.md5(a2 + str10); + String str11 = + resourcePath + + tenant + + token + + APIConfig.USER_TYPE + + sendDate + + APIConfig.CONTENT_ENCRYPTED + + contentType + + bodyEncrypted; + String a5 = HashUtils.md5(a3 + sendDate); + if (!StringUtils.isEmpty(a5) && !StringUtils.isEmpty(str11)) { + return MacUtils.hmacSha256(a5.getBytes(), str11); + } + return ""; + } + + public static String decryptResponse(String timeStamp, String contentType, String cipherText) { + String str4 = timeStamp + APIConfig.CONTENT_ENCRYPTED + contentType; + String a2 = StringUtils.isEmpty(str4) ? "" : HashUtils.md5(str4); + String hashedTimeStamp = HashUtils.md5(timeStamp); + if (!StringUtils.isEmpty(cipherText)) { + return AESUtils.decrypt(cipherText, a2, hashedTimeStamp); + } + return ""; + } + + public static String calculateResponseVerification(String str, String str2, String str3) { + String str4 = str + APIConfig.CONTENT_ENCRYPTED + str2; + String a2 = StringUtils.isEmpty(str4) ? "" : HashUtils.md5(str4); + String str5 = str + APIConfig.CONTENT_ENCRYPTED + str2 + str3; + String a4 = HashUtils.md5(a2 + str); + if (!StringUtils.isEmpty(a4) && !StringUtils.isEmpty(str5)) { + + return MacUtils.hmacSha256(a4.getBytes(), str5); + } + + return ""; + } + + public static final String BASE_URL_P = "https://gateway-mg-eu.soimt.com/api.app/v1/"; + + public static String encryptRequest( + String url, long time, String tenant, String token, String body, String contentType) { + + String sendDate = String.valueOf(time); + // tenant + String replace = !StringUtils.isEmpty(url) ? url.replace(BASE_URL_P, "/") : ""; + String encryptedBody = ""; + if (!StringUtils.isEmpty(body)) { + String sb3 = + HashUtils.md5(replace + tenant + token + APIConfig.USER_TYPE) + + sendDate + + APIConfig.CONTENT_ENCRYPTED + + contentType; + String a2 = HashUtils.md5(sb3); + String a3 = HashUtils.md5(sendDate); + if (!StringUtils.isEmpty(body) && !StringUtils.isEmpty(a2) && !StringUtils.isEmpty(a3)) { + encryptedBody = AESUtils.encrypt(body, a2, a3); + } + } + + if (encryptedBody == null) { + encryptedBody = ""; + } + return encryptedBody; + } + + public static String decryptRequest( + String url, long time, String tenant, String token, String body, String contentType) { + + String timeStamp = String.valueOf(time); + String resourcePath = !StringUtils.isEmpty(url) ? url.replace(BASE_URL_P, "/") : ""; + if (!StringUtils.isEmpty(body)) { + String sb3 = + HashUtils.md5(resourcePath + tenant + token + APIConfig.USER_TYPE) + + timeStamp + + APIConfig.CONTENT_ENCRYPTED + + contentType; + String a2 = HashUtils.md5(sb3); + String timeStampHash = HashUtils.md5(timeStamp); + if (!StringUtils.isEmpty(body) + && !StringUtils.isEmpty(a2) + && !StringUtils.isEmpty(timeStampHash)) { + return AESUtils.decrypt(body, a2, timeStampHash); + } + } + return null; + } +} diff --git a/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/HashUtils.java b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/HashUtils.java new file mode 100644 index 0000000..1277281 --- /dev/null +++ b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/HashUtils.java @@ -0,0 +1,42 @@ +package net.heberling.ismart.java.rest; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class HashUtils { + public static final String MD5 = "MD5"; + public static final String SHA_1 = "SHA1"; + public static final String SHA_256 = "SHA-256"; + + public static String md5(String str) { + try { + MessageDigest messageDigest = MessageDigest.getInstance(MD5); + messageDigest.update(str.getBytes(StandardCharsets.UTF_8)); + byte[] digest = messageDigest.digest(); + return HexUtils.bytesToHex(digest); + } catch (NoSuchAlgorithmException e2) { + throw new RuntimeException(e2); + } + } + + public static String sha1(String str) { + try { + MessageDigest messageDigest = MessageDigest.getInstance(SHA_1); + messageDigest.update(str.getBytes(StandardCharsets.UTF_8)); + return HexUtils.bytesToHex(messageDigest.digest()); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + public static String sha256(String str) { + try { + MessageDigest messageDigest = MessageDigest.getInstance(SHA_256); + messageDigest.update(str.getBytes(StandardCharsets.UTF_8)); + return HexUtils.bytesToHex(messageDigest.digest()); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } +} diff --git a/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/HexUtils.java b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/HexUtils.java new file mode 100644 index 0000000..e654c39 --- /dev/null +++ b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/HexUtils.java @@ -0,0 +1,32 @@ +package net.heberling.ismart.java.rest; + +public class HexUtils { + + public static String bytesToHex(byte[] byteArray) { + StringBuilder hexStringBuffer = new StringBuilder(); + for (int i = 0; i < byteArray.length; i++) { + hexStringBuffer.append(byteToHex(byteArray[i])); + } + return hexStringBuffer.toString(); + } + + public static String byteToHex(byte num) { + return String.format("%02x", num); + } + + public static byte[] hexToBytes(String hexString) { + if (hexString.length() % 2 == 1) { + throw new IllegalArgumentException("Invalid hexadecimal String supplied."); + } + + byte[] bytes = new byte[hexString.length() / 2]; + for (int i = 0; i < hexString.length(); i += 2) { + bytes[i / 2] = hexToByte(hexString.substring(i, i + 2)); + } + return bytes; + } + + public static byte hexToByte(String str) { + return (byte) Integer.parseInt(str, 16); + } +} diff --git a/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/MacUtils.java b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/MacUtils.java new file mode 100644 index 0000000..df7a5dd --- /dev/null +++ b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/MacUtils.java @@ -0,0 +1,21 @@ +package net.heberling.ismart.java.rest; + +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +public class MacUtils { + private static final String HMAC_SHA_256 = "HmacSHA256"; + + public static String hmacSha256(byte[] bArr, String str) { + try { + Mac mac = Mac.getInstance(HMAC_SHA_256); + mac.init(new SecretKeySpec(bArr, HMAC_SHA_256)); + return HexUtils.bytesToHex(mac.doFinal(str.getBytes(StandardCharsets.UTF_8))); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + throw new RuntimeException(e); + } + } +} diff --git a/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/StringUtils.java b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/StringUtils.java new file mode 100644 index 0000000..a4b1e9c --- /dev/null +++ b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/StringUtils.java @@ -0,0 +1,7 @@ +package net.heberling.ismart.java.rest; + +public class StringUtils { + public static boolean isEmpty(String str2) { + return str2 == null || str2.isEmpty(); + } +} diff --git a/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/JsonResponseMessage.java b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/JsonResponseMessage.java new file mode 100644 index 0000000..5356761 --- /dev/null +++ b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/JsonResponseMessage.java @@ -0,0 +1,54 @@ +package net.heberling.ismart.java.rest.api.v1; + +/** + * @author Doug Culnane + */ +public abstract class JsonResponseMessage { + + public static final Integer CODE_SUCCESS = 0; + + Integer code; + String message; + String eventId; + + abstract Object getData(); + + public boolean hasData() { + return getData() != null; + } + + public String getEventId() { + return eventId; + } + + public void setEventId(String eventId) { + this.eventId = eventId; + } + + public JsonResponseMessage() {} + + public Integer getCode() { + return code; + } + + public void setCode(Integer code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + @Override + public String toString() { + return "[" + code + "] " + message; + } + + public boolean isSuccess() { + return code == CODE_SUCCESS; + } +} diff --git a/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/MessageList.java b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/MessageList.java new file mode 100644 index 0000000..1cdbb8e --- /dev/null +++ b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/MessageList.java @@ -0,0 +1,17 @@ +package net.heberling.ismart.java.rest.api.v1; + +/** + * @author Doug Culnane + */ +public class MessageList extends JsonResponseMessage { + + String data; + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } +} diff --git a/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/MessageNotificationList.java b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/MessageNotificationList.java new file mode 100644 index 0000000..03c5ceb --- /dev/null +++ b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/MessageNotificationList.java @@ -0,0 +1,113 @@ +package net.heberling.ismart.java.rest.api.v1; + +/** + * @author Doug Culnane + */ +public class MessageNotificationList extends JsonResponseMessage { + + MessageNotificationListData data; + + public MessageNotificationListData getData() { + return data; + } + + public void setData(MessageNotificationListData data) { + this.data = data; + } + + public class MessageNotificationListData { + Long recordsNumber; + Notification[] notifications; + + public Long getRecordsNumber() { + return recordsNumber; + } + + public void setRecordsNumber(Long recordsNumber) { + this.recordsNumber = recordsNumber; + } + + public Notification[] getNotifications() { + return notifications; + } + + public void setNotifications(Notification[] notifications) { + this.notifications = notifications; + } + } + + public class Notification { + Integer readStatus; + String messageTime; + String messageType; + String sender; + Long messageId; + String vin; + String title; + String content; + + public Integer getReadStatus() { + return readStatus; + } + + public void setReadStatus(Integer readStatus) { + this.readStatus = readStatus; + } + + public String getMessageTime() { + return messageTime; + } + + public void setMessageTime(String messageTime) { + this.messageTime = messageTime; + } + + public String getMessageType() { + return messageType; + } + + public void setMessageType(String messageType) { + this.messageType = messageType; + } + + public String getSender() { + return sender; + } + + public void setSender(String sender) { + this.sender = sender; + } + + public Long getMessageId() { + return messageId; + } + + public void setMessageId(Long messageId) { + this.messageId = messageId; + } + + public String getTitle() { + return title; + } + + public String getVin() { + return vin; + } + + public void setVin(String vin) { + this.vin = vin; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + } +} diff --git a/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/OauthToken.java b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/OauthToken.java new file mode 100644 index 0000000..a7778cf --- /dev/null +++ b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/OauthToken.java @@ -0,0 +1,194 @@ +package net.heberling.ismart.java.rest.api.v1; + +/** + * @author Doug Culnane + */ +public class OauthToken extends JsonResponseMessage { + + OauthTokenData data; + Boolean IS_CHANGE_PASSWORD; + String dept_id; + Long expires_in; + String account; + + public OauthTokenData getData() { + return data; + } + + public void setData(OauthTokenData data) { + this.data = data; + } + + public Boolean getIS_CHANGE_PASSWORD() { + return IS_CHANGE_PASSWORD; + } + + public void setIS_CHANGE_PASSWORD(Boolean iS_CHANGE_PASSWORD) { + IS_CHANGE_PASSWORD = iS_CHANGE_PASSWORD; + } + + public String getDept_id() { + return dept_id; + } + + public void setDept_id(String dept_id) { + this.dept_id = dept_id; + } + + public Long getExpires_in() { + return expires_in; + } + + public void setExpires_in(Long expires_in) { + this.expires_in = expires_in; + } + + public String getAccount() { + return account; + } + + public void setAccount(String account) { + this.account = account; + } + + public class OauthTokenData { + + public OauthTokenData() {} + + String tenant_id; + String languageType; + String user_name; + String avatar; + String token_type; + String client_id; + String access_token; + String role_name; + String refresh_token; + String license; + String post_id; + String user_id; + String role_id; + String scope; + String oauth_id; + + public String getTenant_id() { + return tenant_id; + } + + public void setTenant_id(String tenant_id) { + this.tenant_id = tenant_id; + } + + public String getLanguageType() { + return languageType; + } + + public void setLanguageType(String languageType) { + this.languageType = languageType; + } + + public String getUser_name() { + return user_name; + } + + public void setUser_name(String user_name) { + this.user_name = user_name; + } + + public String getAvatar() { + return avatar; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getToken_type() { + return token_type; + } + + public void setToken_type(String token_type) { + this.token_type = token_type; + } + + public String getClient_id() { + return client_id; + } + + public void setClient_id(String client_id) { + this.client_id = client_id; + } + + public String getAccess_token() { + return access_token; + } + + public void setAccess_token(String access_token) { + this.access_token = access_token; + } + + public String getRole_name() { + return role_name; + } + + public void setRole_name(String role_name) { + this.role_name = role_name; + } + + public String getRefresh_token() { + return refresh_token; + } + + public void setRefresh_token(String refresh_token) { + this.refresh_token = refresh_token; + } + + public String getLicense() { + return license; + } + + public void setLicense(String license) { + this.license = license; + } + + public String getPost_id() { + return post_id; + } + + public void setPost_id(String post_id) { + this.post_id = post_id; + } + + public String getUser_id() { + return user_id; + } + + public void setUser_id(String user_id) { + this.user_id = user_id; + } + + public String getRole_id() { + return role_id; + } + + public void setRole_id(String role_id) { + this.role_id = role_id; + } + + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } + + public String getOauth_id() { + return oauth_id; + } + + public void setOauth_id(String oauth_id) { + this.oauth_id = oauth_id; + } + } +} diff --git a/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/VechicleChargingMgmtData.java b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/VechicleChargingMgmtData.java new file mode 100644 index 0000000..3622662 --- /dev/null +++ b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/VechicleChargingMgmtData.java @@ -0,0 +1,392 @@ +package net.heberling.ismart.java.rest.api.v1; + +/** + * @author Doug Culnane + */ +public class VechicleChargingMgmtData extends JsonResponseMessage { + + ViechicleChargingMgmtDataData data; + + public ViechicleChargingMgmtDataData getData() { + return data; + } + + public void setData(ViechicleChargingMgmtDataData data) { + this.data = data; + } + + public class ViechicleChargingMgmtDataData { + public RvsChargeStatus getRvsChargeStatus() { + return rvsChargeStatus; + } + + public void setRvsChargeStatus(RvsChargeStatus rvsChargeStatus) { + this.rvsChargeStatus = rvsChargeStatus; + } + + public ChrgMgmtData getChrgMgmtData() { + return chrgMgmtData; + } + + public void setChrgMgmtData(ChrgMgmtData chrgMgmtData) { + this.chrgMgmtData = chrgMgmtData; + } + + RvsChargeStatus rvsChargeStatus; + ChrgMgmtData chrgMgmtData; + } + + public class RvsChargeStatus { + + Integer mileageSinceLastCharge; + Integer totalBatteryCapacity; + Integer workingVoltage; + Integer chargingDuration; + Integer chargingType; + Integer lastChargeEndingPower; + + /** Value / 10 = Range */ + Integer fuelRangeElec; + + Integer realtimePower; + Integer workingCurrent; + + /** Gun connected 0 = Disconnected 1 = Connected */ + Integer chargingGunState; + + Integer mileageOfDay; + Long startTime; + Long endTime; + Integer powerUsageOfDay; + Integer powerUsageSinceLastCharge; + + /** Odometer */ + Integer mileage; + + public Integer getMileageSinceLastCharge() { + return mileageSinceLastCharge; + } + + public void setMileageSinceLastCharge(Integer mileageSinceLastCharge) { + this.mileageSinceLastCharge = mileageSinceLastCharge; + } + + public Integer getTotalBatteryCapacity() { + return totalBatteryCapacity; + } + + public void setTotalBatteryCapacity(Integer totalBatteryCapacity) { + this.totalBatteryCapacity = totalBatteryCapacity; + } + + public Integer getWorkingVoltage() { + return workingVoltage; + } + + public void setWorkingVoltage(Integer workingVoltage) { + this.workingVoltage = workingVoltage; + } + + public Integer getChargingDuration() { + return chargingDuration; + } + + public void setChargingDuration(Integer chargingDuration) { + this.chargingDuration = chargingDuration; + } + + public Integer getChargingType() { + return chargingType; + } + + public void setChargingType(Integer chargingType) { + this.chargingType = chargingType; + } + + public Integer getLastChargeEndingPower() { + return lastChargeEndingPower; + } + + public void setLastChargeEndingPower(Integer lastChargeEndingPower) { + this.lastChargeEndingPower = lastChargeEndingPower; + } + + public Integer getFuelRangeElec() { + return fuelRangeElec; + } + + public void setFuelRangeElec(Integer fuelRangeElec) { + this.fuelRangeElec = fuelRangeElec; + } + + public Integer getRealtimePower() { + return realtimePower; + } + + public void setRealtimePower(Integer realtimePower) { + this.realtimePower = realtimePower; + } + + public Integer getWorkingCurrent() { + return workingCurrent; + } + + public void setWorkingCurrent(Integer workingCurrent) { + this.workingCurrent = workingCurrent; + } + + public Integer getChargingGunState() { + return chargingGunState; + } + + public void setChargingGunState(Integer chargingGunState) { + this.chargingGunState = chargingGunState; + } + + public Integer getMileageOfDay() { + return mileageOfDay; + } + + public void setMileageOfDay(Integer mileageOfDay) { + this.mileageOfDay = mileageOfDay; + } + + public Long getStartTime() { + return startTime; + } + + public void setStartTime(Long startTime) { + this.startTime = startTime; + } + + public Long getEndTime() { + return endTime; + } + + public void setEndTime(Long endTime) { + this.endTime = endTime; + } + + public Integer getPowerUsageOfDay() { + return powerUsageOfDay; + } + + public void setPowerUsageOfDay(Integer powerUsageOfDay) { + this.powerUsageOfDay = powerUsageOfDay; + } + + public Integer getPowerUsageSinceLastCharge() { + return powerUsageSinceLastCharge; + } + + public void setPowerUsageSinceLastCharge(Integer powerUsageSinceLastCharge) { + this.powerUsageSinceLastCharge = powerUsageSinceLastCharge; + } + + public Integer getMileage() { + return mileage; + } + + public void setMileage(Integer mileage) { + this.mileage = mileage; + } + } + + public class ChrgMgmtData { + + Integer clstrElecRngToEPT; + + /** BMS Charging Status 0 = Not charging. 1 = Charging 5 = Waiting for Charger */ + Integer bmsChrgSts; + + Integer bmsPackSOCDsp; + Integer bmsPTCHeatReqDspCmd; + Integer bmsPackCrnt; + Integer bmsChrgCtrlDspCmd; + Integer bmsReserStMintueDspCmd; + Integer bmsOnBdChrgTrgtSOCDspCmd; + Integer bmsEstdElecRng; + Integer bmsReserCtrlDspCmd; + Integer bmsChrgOtptCrntReq; + Integer bmsPTCHeatSpRsn; + Integer bmsReserStHourDspCmd; + Integer bmsPackVol; + Integer bmsReserSpHourDspCmd; + Integer bmsAdpPubChrgSttnDspCmd; + Integer chrgngRmnngTimeV; + Integer bmsReserSpMintueDspCmd; + Integer bmsAltngChrgCrntDspCmd; + Integer bmsChrgSpRsn; + + /** Charging remaining time */ + Integer chrgngRmnngTime; + + public Integer getClstrElecRngToEPT() { + return clstrElecRngToEPT; + } + + public void setClstrElecRngToEPT(Integer clstrElecRngToEPT) { + this.clstrElecRngToEPT = clstrElecRngToEPT; + } + + public Integer getBmsChrgSts() { + return bmsChrgSts; + } + + public void setBmsChrgSts(Integer bmsChrgSts) { + this.bmsChrgSts = bmsChrgSts; + } + + public Integer getBmsPackSOCDsp() { + return bmsPackSOCDsp; + } + + public void setBmsPackSOCDsp(Integer bmsPackSOCDsp) { + this.bmsPackSOCDsp = bmsPackSOCDsp; + } + + public Integer getBmsPTCHeatReqDspCmd() { + return bmsPTCHeatReqDspCmd; + } + + public void setBmsPTCHeatReqDspCmd(Integer bmsPTCHeatReqDspCmd) { + this.bmsPTCHeatReqDspCmd = bmsPTCHeatReqDspCmd; + } + + public Integer getBmsPackCrnt() { + return bmsPackCrnt; + } + + public void setBmsPackCrnt(Integer bmsPackCrnt) { + this.bmsPackCrnt = bmsPackCrnt; + } + + public Integer getBmsChrgCtrlDspCmd() { + return bmsChrgCtrlDspCmd; + } + + public void setBmsChrgCtrlDspCmd(Integer bmsChrgCtrlDspCmd) { + this.bmsChrgCtrlDspCmd = bmsChrgCtrlDspCmd; + } + + public Integer getBmsReserStMintueDspCmd() { + return bmsReserStMintueDspCmd; + } + + public void setBmsReserStMintueDspCmd(Integer bmsReserStMintueDspCmd) { + this.bmsReserStMintueDspCmd = bmsReserStMintueDspCmd; + } + + public Integer getBmsOnBdChrgTrgtSOCDspCmd() { + return bmsOnBdChrgTrgtSOCDspCmd; + } + + public void setBmsOnBdChrgTrgtSOCDspCmd(Integer bmsOnBdChrgTrgtSOCDspCmd) { + this.bmsOnBdChrgTrgtSOCDspCmd = bmsOnBdChrgTrgtSOCDspCmd; + } + + public Integer getBmsEstdElecRng() { + return bmsEstdElecRng; + } + + public void setBmsEstdElecRng(Integer bmsEstdElecRng) { + this.bmsEstdElecRng = bmsEstdElecRng; + } + + public Integer getBmsReserCtrlDspCmd() { + return bmsReserCtrlDspCmd; + } + + public void setBmsReserCtrlDspCmd(Integer bmsReserCtrlDspCmd) { + this.bmsReserCtrlDspCmd = bmsReserCtrlDspCmd; + } + + public Integer getBmsChrgOtptCrntReq() { + return bmsChrgOtptCrntReq; + } + + public void setBmsChrgOtptCrntReq(Integer bmsChrgOtptCrntReq) { + this.bmsChrgOtptCrntReq = bmsChrgOtptCrntReq; + } + + public Integer getBmsPTCHeatSpRsn() { + return bmsPTCHeatSpRsn; + } + + public void setBmsPTCHeatSpRsn(Integer bmsPTCHeatSpRsn) { + this.bmsPTCHeatSpRsn = bmsPTCHeatSpRsn; + } + + public Integer getBmsReserStHourDspCmd() { + return bmsReserStHourDspCmd; + } + + public void setBmsReserStHourDspCmd(Integer bmsReserStHourDspCmd) { + this.bmsReserStHourDspCmd = bmsReserStHourDspCmd; + } + + public Integer getBmsPackVol() { + return bmsPackVol; + } + + public void setBmsPackVol(Integer bmsPackVol) { + this.bmsPackVol = bmsPackVol; + } + + public Integer getBmsReserSpHourDspCmd() { + return bmsReserSpHourDspCmd; + } + + public void setBmsReserSpHourDspCmd(Integer bmsReserSpHourDspCmd) { + this.bmsReserSpHourDspCmd = bmsReserSpHourDspCmd; + } + + public Integer getBmsAdpPubChrgSttnDspCmd() { + return bmsAdpPubChrgSttnDspCmd; + } + + public void setBmsAdpPubChrgSttnDspCmd(Integer bmsAdpPubChrgSttnDspCmd) { + this.bmsAdpPubChrgSttnDspCmd = bmsAdpPubChrgSttnDspCmd; + } + + public Integer getChrgngRmnngTimeV() { + return chrgngRmnngTimeV; + } + + public void setChrgngRmnngTimeV(Integer chrgngRmnngTimeV) { + this.chrgngRmnngTimeV = chrgngRmnngTimeV; + } + + public Integer getBmsReserSpMintueDspCmd() { + return bmsReserSpMintueDspCmd; + } + + public void setBmsReserSpMintueDspCmd(Integer bmsReserSpMintueDspCmd) { + this.bmsReserSpMintueDspCmd = bmsReserSpMintueDspCmd; + } + + public Integer getBmsAltngChrgCrntDspCmd() { + return bmsAltngChrgCrntDspCmd; + } + + public void setBmsAltngChrgCrntDspCmd(Integer bmsAltngChrgCrntDspCmd) { + this.bmsAltngChrgCrntDspCmd = bmsAltngChrgCrntDspCmd; + } + + public Integer getBmsChrgSpRsn() { + return bmsChrgSpRsn; + } + + public void setBmsChrgSpRsn(Integer bmsChrgSpRsn) { + this.bmsChrgSpRsn = bmsChrgSpRsn; + } + + public Integer getChrgngRmnngTime() { + return chrgngRmnngTime; + } + + public void setChrgngRmnngTime(Integer chrgngRmnngTime) { + this.chrgngRmnngTime = chrgngRmnngTime; + } + } +} diff --git a/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/VehicleCcInfo.java b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/VehicleCcInfo.java new file mode 100644 index 0000000..e086ec5 --- /dev/null +++ b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/VehicleCcInfo.java @@ -0,0 +1,19 @@ +package net.heberling.ismart.java.rest.api.v1; + +/** + * Returns null...? + * + * @author Doug Culnane + */ +public class VehicleCcInfo extends JsonResponseMessage { + + String data; + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } +} diff --git a/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/VehicleList.java b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/VehicleList.java new file mode 100644 index 0000000..7f98cb3 --- /dev/null +++ b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/VehicleList.java @@ -0,0 +1,166 @@ +package net.heberling.ismart.java.rest.api.v1; + +/** + * @author Doug Culnane + */ +public class VehicleList extends JsonResponseMessage { + + VinListData data; + + public VinListData getData() { + return data; + } + + public void setData(VinListData data) { + this.data = data; + } + + public class VinListData { + public VinListData() {} + + VinListItem[] vinList; + + public VinListItem[] getVinList() { + return vinList; + } + + public void setVinList(VinListItem[] vinList) { + this.vinList = vinList; + } + } + + public class VinListItem { + Long bindTime; + String colorName; + String brandName; + String modelName; + String modelYear; + String series; + Boolean isActivate; + Boolean isCurrentVehicle; + Boolean isSubaccount; + String vin; + VehicleModelConfiguration[] vehicleModelConfiguration; + + public VinListItem() {} + + public Long getBindTime() { + return bindTime; + } + + public void setBindTime(Long bindTime) { + this.bindTime = bindTime; + } + + public String getColorName() { + return colorName; + } + + public void setColorName(String colorName) { + this.colorName = colorName; + } + + public String getBrandName() { + return brandName; + } + + public void setBrandName(String brandName) { + this.brandName = brandName; + } + + public String getModelName() { + return modelName; + } + + public void setModelName(String modelName) { + this.modelName = modelName; + } + + public String getModelYear() { + return modelYear; + } + + public void setModelYear(String modelYear) { + this.modelYear = modelYear; + } + + public String getSeries() { + return series; + } + + public void setSeries(String series) { + this.series = series; + } + + public Boolean getIsActivate() { + return isActivate; + } + + public void setIsActivate(Boolean isActivate) { + this.isActivate = isActivate; + } + + public Boolean getIsCurrentVehicle() { + return isCurrentVehicle; + } + + public void setIsCurrentVehicle(Boolean isCurrentVehicle) { + this.isCurrentVehicle = isCurrentVehicle; + } + + public Boolean getIsSubaccount() { + return isSubaccount; + } + + public void setIsSubaccount(Boolean isSubaccount) { + this.isSubaccount = isSubaccount; + } + + public String getVin() { + return vin; + } + + public void setVin(String vin) { + this.vin = vin; + } + + public VehicleModelConfiguration[] getVehicleModelConfiguration() { + return vehicleModelConfiguration; + } + + public void setVehicleModelConfiguration( + VehicleModelConfiguration[] vehicleModelConfiguration) { + this.vehicleModelConfiguration = vehicleModelConfiguration; + } + } + + public class VehicleModelConfiguration { + String itemName; + String itemCode; + String itemValue; + + public String getItemName() { + return itemName; + } + + public void setItemName(String itemName) { + this.itemName = itemName; + } + + public String getItemCode() { + return itemCode; + } + + public void setItemCode(String itemCode) { + this.itemCode = itemCode; + } + + public String getItemValue() { + return itemValue; + } + + public void setItemValue(String itemValue) { + this.itemValue = itemValue; + } + } +} diff --git a/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/VehicleLocation.java b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/VehicleLocation.java new file mode 100644 index 0000000..ec691ff --- /dev/null +++ b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/VehicleLocation.java @@ -0,0 +1,17 @@ +package net.heberling.ismart.java.rest.api.v1; + +/** + * @author Doug Culnane + */ +public class VehicleLocation extends JsonResponseMessage { + + String data; + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } +} diff --git a/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/VehicleStatisticsBasicInfo.java b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/VehicleStatisticsBasicInfo.java new file mode 100644 index 0000000..b3827b6 --- /dev/null +++ b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/VehicleStatisticsBasicInfo.java @@ -0,0 +1,43 @@ +package net.heberling.ismart.java.rest.api.v1; + +/** + * @author Doug Culnane + */ +public class VehicleStatisticsBasicInfo extends JsonResponseMessage { + + VehicleStatisticsBasicInfoData data; + + public VehicleStatisticsBasicInfo() {} + + public VehicleStatisticsBasicInfoData getData() { + return data; + } + + public void setData(VehicleStatisticsBasicInfoData data) { + this.data = data; + } + + public class VehicleStatisticsBasicInfoData { + + String vin; + Long initialDate; + + public VehicleStatisticsBasicInfoData() {} + + public String getVin() { + return vin; + } + + public void setVin(String vin) { + this.vin = vin; + } + + public Long getInitialDate() { + return initialDate; + } + + public void setInitialDate(Long initialDate) { + this.initialDate = initialDate; + } + } +} diff --git a/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/VehicleStatisticsInfo.java b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/VehicleStatisticsInfo.java new file mode 100644 index 0000000..3a24d86 --- /dev/null +++ b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/VehicleStatisticsInfo.java @@ -0,0 +1,19 @@ +package net.heberling.ismart.java.rest.api.v1; + +/** + * Returns null...? + * + * @author Doug Culnane + */ +public class VehicleStatisticsInfo extends JsonResponseMessage { + + String data; + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } +} diff --git a/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/VehicleStatus.java b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/VehicleStatus.java new file mode 100644 index 0000000..5a4af35 --- /dev/null +++ b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/api/v1/VehicleStatus.java @@ -0,0 +1,17 @@ +package net.heberling.ismart.java.rest.api.v1; + +/** + * @author Doug Culnane + */ +public class VehicleStatus extends JsonResponseMessage { + + String data; + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } +} diff --git a/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/exceptions/ChargingStatusAPIException.java b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/exceptions/ChargingStatusAPIException.java new file mode 100644 index 0000000..5f74c20 --- /dev/null +++ b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/exceptions/ChargingStatusAPIException.java @@ -0,0 +1,13 @@ +package net.heberling.ismart.java.rest.exceptions; + +import net.heberling.ismart.java.rest.api.v1.JsonResponseMessage; + +/** + * @author Doug Culnane - Initial contribution + */ +public class ChargingStatusAPIException extends Exception { + + public ChargingStatusAPIException(JsonResponseMessage response) { + super("[" + response.getCode() + "] " + response.getMessage()); + } +} diff --git a/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/exceptions/VehicleStatusAPIException.java b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/exceptions/VehicleStatusAPIException.java new file mode 100644 index 0000000..6ebaed8 --- /dev/null +++ b/saic-java-rest-api/src/main/java/net/heberling/ismart/java/rest/exceptions/VehicleStatusAPIException.java @@ -0,0 +1,13 @@ +package net.heberling.ismart.java.rest.exceptions; + +import net.heberling.ismart.java.rest.api.v1.JsonResponseMessage; + +/** + * @author Doug Culnane - Initial contribution + */ +public class VehicleStatusAPIException extends Exception { + + public VehicleStatusAPIException(JsonResponseMessage response) { + super("[" + response.getCode() + "] " + response.getMessage()); + } +} diff --git a/saic-java-rest-client/pom.xml b/saic-java-rest-client/pom.xml new file mode 100644 index 0000000..761806f --- /dev/null +++ b/saic-java-rest-client/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + + io.github.saic-ismart-api + saic-ismart-api-parent + 0.0.0-SNAPSHOT + + + saic-java-rest-client + SAIC Java REST API Client + Implementation of the SAIC Rest API Client in Java + + + + io.github.saic-ismart-api + saic-java-rest-api + 0.0.0-SNAPSHOT + + + org.eclipse.jetty + jetty-client + 9.4.54.v20240208 + + + org.slf4j + slf4j-api + 2.0.12 + + + com.google.code.gson + gson + 2.8.9 + + + org.junit.jupiter + junit-jupiter-api + 5.10.0 + test + + + diff --git a/saic-java-rest-client/src/main/java/net/heberling/ismart/java/rest/SaicApiClient.java b/saic-java-rest-client/src/main/java/net/heberling/ismart/java/rest/SaicApiClient.java new file mode 100644 index 0000000..cac2ea7 --- /dev/null +++ b/saic-java-rest-client/src/main/java/net/heberling/ismart/java/rest/SaicApiClient.java @@ -0,0 +1,334 @@ +package net.heberling.ismart.java.rest; + +import com.google.gson.Gson; +import java.io.IOException; +import java.net.URI; +import java.util.Random; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import net.heberling.ismart.java.rest.api.v1.JsonResponseMessage; +import net.heberling.ismart.java.rest.api.v1.MessageList; +import net.heberling.ismart.java.rest.api.v1.MessageNotificationList; +import net.heberling.ismart.java.rest.api.v1.OauthToken; +import net.heberling.ismart.java.rest.api.v1.VechicleChargingMgmtData; +import net.heberling.ismart.java.rest.api.v1.VehicleCcInfo; +import net.heberling.ismart.java.rest.api.v1.VehicleList; +import net.heberling.ismart.java.rest.api.v1.VehicleLocation; +import net.heberling.ismart.java.rest.api.v1.VehicleStatisticsBasicInfo; +import net.heberling.ismart.java.rest.api.v1.VehicleStatus; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.http.HttpMethod; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * SIAC API HTTP Client implementation using org.eclipse.jetty.client. + * + * @author Doug Culnane + */ +public class SaicApiClient { + + private HttpClient httpClient; + + private byte[] deviceId; + + private final Logger logger = LoggerFactory.getLogger(SaicApiClient.class); + + private final String URL_ROOT_V1 = "https://gateway-mg-eu.soimt.com/api.app/v1/"; + + public SaicApiClient(HttpClient httpClient) { + this.httpClient = httpClient; + } + + public OauthToken getOauthToken(String username, String password, String language) + throws IOException, InterruptedException, TimeoutException, ExecutionException { + if (deviceId == null) { + deviceId = new byte[64]; + new Random().nextBytes(deviceId); + } + return sendRequest( + new OauthToken(), + URL_ROOT_V1 + "oauth/token", + HttpMethod.POST, + "grant_type=password&username=" + + username + + "&password=" + + HashUtils.sha1(password) + + "&scope=all&deviceId=" + + HexUtils.bytesToHex(deviceId) + + "###europecar&deviceType=1&loginType=" + + getLoginType(username) + + "&language=" + + language, + "application/x-www-form-urlencoded", + "", + ""); + } + + /** + * "2" if username_is_email else "1", + * + * @param username + * @return + */ + private int getLoginType(String username) { + if (username.contains("@")) { + return 2; + } + return 1; + } + + public VehicleList getVehicleList(String token) + throws IOException, InterruptedException, TimeoutException, ExecutionException { + return sendRequest( + new VehicleList(), + URL_ROOT_V1 + "vehicle/list", + HttpMethod.GET, + "", + "application/json", + token, + ""); + } + + public VehicleStatus getVehicleStatus(String token, String vin) + throws IOException, InterruptedException, TimeoutException, ExecutionException { + return sendRequest( + new VehicleStatus(), + URL_ROOT_V1 + "vehicle/status?vin=" + HashUtils.sha256(vin) + "&vehStatusReqType=2", + HttpMethod.GET, + "", + "application/json", + token, + ""); + } + + public VechicleChargingMgmtData getVehicleChargingMgmtData(String token, String vin) + throws IOException, InterruptedException, TimeoutException, ExecutionException { + VechicleChargingMgmtData message1 = + sendRequest( + new VechicleChargingMgmtData(), + URL_ROOT_V1 + "vehicle/charging/mgmtData?vin=" + HashUtils.sha256(vin), + HttpMethod.GET, + "", + "application/json", + token, + ""); + if (message1.isSuccess()) { + return sendRequest( + new VechicleChargingMgmtData(), + URL_ROOT_V1 + "vehicle/charging/mgmtData?vin=" + HashUtils.sha256(vin), + HttpMethod.GET, + "", + "application/json", + token, + message1.getEventId()); + } + return message1; + } + + public VehicleStatisticsBasicInfo getVehicleStatisticsBasicInfo(String token, String vin) + throws IOException, InterruptedException, TimeoutException, ExecutionException { + return sendRequest( + new VehicleStatisticsBasicInfo(), + URL_ROOT_V1 + "vehicle/statisticsBasicInfo?vin=" + HashUtils.sha256(vin), + HttpMethod.GET, + "", + "application/json", + token, + ""); + } + + public VehicleStatus getVehicleStatusClassified(String token, String vin) + throws IOException, InterruptedException, TimeoutException, ExecutionException { + return sendRequest( + new VehicleStatus(), + URL_ROOT_V1 + "vehicle/status/classified?vin=" + HashUtils.sha256(vin), + HttpMethod.GET, + "", + "application/json", + token, + ""); + } + + public VehicleCcInfo getVehicleCcInfo(String token, String vin) + throws IOException, InterruptedException, TimeoutException, ExecutionException { + VehicleCcInfo message1 = + sendRequest( + new VehicleCcInfo(), + URL_ROOT_V1 + "vehicle/vehicle/ccInfo?vin=" + HashUtils.sha256(vin), + HttpMethod.GET, + "", + "application/json", + token, + ""); + if (message1.isSuccess()) { + return sendRequest( + new VehicleCcInfo(), + URL_ROOT_V1 + "vehicle/vehicle/ccInfo?vin=" + HashUtils.sha256(vin), + HttpMethod.GET, + "", + "application/json", + token, + message1.getEventId()); + } + return message1; + } + + public VehicleLocation getVehicleLocation(String token, String vin) + throws IOException, InterruptedException, TimeoutException, ExecutionException { + VehicleLocation message1 = + sendRequest( + new VehicleLocation(), + URL_ROOT_V1 + "vehicle/location?vin=" + HashUtils.sha256(vin), + HttpMethod.GET, + "", + "application/json", + token, + ""); + if (message1.isSuccess()) { + return sendRequest( + new VehicleLocation(), + URL_ROOT_V1 + "vehicle/location?vin=" + HashUtils.sha256(vin), + HttpMethod.GET, + "", + "application/json", + token, + message1.getEventId()); + } + return message1; + } + + public MessageList getMessageList(String token, String vin) + throws IOException, InterruptedException, TimeoutException, ExecutionException { + return sendRequest( + new MessageList(), + URL_ROOT_V1 + "message/list?vin=" + HashUtils.sha256(vin), + HttpMethod.GET, + "", + "application/json", + token, + ""); + } + + public MessageList getVehicleInfo(String token, String vin) + throws IOException, InterruptedException, TimeoutException, ExecutionException { + return sendRequest( + new MessageList(), + URL_ROOT_V1 + "vehicle/info?vin=" + HashUtils.sha256(vin), + HttpMethod.GET, + "", + "application/json", + token, + ""); + } + + public JsonResponseMessage getVehicleGeoFence(String token, String vin) + throws IOException, InterruptedException, TimeoutException, ExecutionException { + return sendRequest( + new VehicleStatus(), + URL_ROOT_V1 + "vehicle/geoFence?vin=" + HashUtils.sha256(vin), + HttpMethod.GET, + "", + "application/json", + token, + ""); + } + + public MessageNotificationList getMessageNotificationList(String token) + throws IOException, InterruptedException, TimeoutException, ExecutionException { + return sendRequest( + new MessageNotificationList(), + URL_ROOT_V1 + "message/notificationList", + HttpMethod.GET, + "", + "application/json", + token, + ""); + } + + private T sendRequest( + T responseMessage, + String url, + HttpMethod httpMethod, + String requestBody, + String contentType, + String token, + String eventId) + throws IOException, InterruptedException, TimeoutException, ExecutionException { + + Gson gson = new Gson(); + URI endpoint = URI.create(url); + + long appSendDate = System.currentTimeMillis(); + + Request request = + this.httpClient + .newRequest(endpoint) + .method(httpMethod) + .header("tenant-id", APIConfig.TENANT_ID) + .header("user-type", APIConfig.USER_TYPE) + .header("app-send-date", "" + appSendDate) + .header("app-content-encrypted", APIConfig.CONTENT_ENCRYPTED) + .header("Authorization", APIConfig.PARAM_AUTHENTICATION) + .header("original-content-type", contentType); + + String encryptedBody = ""; + if (requestBody != null && !requestBody.isBlank()) { + encryptedBody = + EncryptionUtils.encryptRequest( + endpoint.toString(), + appSendDate, + APIConfig.TENANT_ID, + token, + requestBody, + contentType); + request.content(new StringContentProvider(encryptedBody), contentType); + } + + String replace = + !StringUtils.isEmpty(endpoint.toString()) + ? endpoint.toString().replace(EncryptionUtils.BASE_URL_P, "/") + : ""; + request.header( + "app-verification-string", + EncryptionUtils.calculateRequestVerification( + replace, appSendDate, APIConfig.TENANT_ID, contentType, encryptedBody, token)); + + if (token != null && !token.isBlank()) { + request.header("blade-auth", token); + } + if (eventId != null && !eventId.isBlank()) { + request.header("event-id", eventId); + } + if (logger.isDebugEnabled()) { + logger.debug("Request: {}", request); + } + + ContentResponse response = request.send(); + if (response.getHeaders().get("app-content-encrypted") != null + && response.getHeaders().get("app-content-encrypted").equals("1")) { + String responseEncripted = response.getContentAsString(); + + String decryptedString = + EncryptionUtils.decryptResponse( + response.getHeaders().get("app-send-date"), + response.getHeaders().get("original-content-type"), + responseEncripted); + if (logger.isDebugEnabled()) { + logger.debug("Response: {}", decryptedString); + } + responseMessage = (T) gson.fromJson(decryptedString, responseMessage.getClass()); + } else { + logger.warn("Unexpected Response: {}", response.getContentAsString()); + } + + if (response.getHeaders().get("event-id") != null) { + responseMessage.setEventId(response.getHeaders().get("event-id")); + } + + return responseMessage; + } +} diff --git a/saic-java-rest-client/src/test/java/net/heberling/ismart/java/rest/TestJsonResponseMessages.java b/saic-java-rest-client/src/test/java/net/heberling/ismart/java/rest/TestJsonResponseMessages.java new file mode 100644 index 0000000..8250d6e --- /dev/null +++ b/saic-java-rest-client/src/test/java/net/heberling/ismart/java/rest/TestJsonResponseMessages.java @@ -0,0 +1,59 @@ +package net.heberling.ismart.java.rest; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.stream.Collectors; +import net.heberling.ismart.java.rest.api.v1.OauthToken; +import net.heberling.ismart.java.rest.api.v1.VehicleList; +import org.junit.jupiter.api.Test; + +/** + * @author Doug Culnane + */ +public class TestJsonResponseMessages { + + @Test + void testOauthToken() throws JsonSyntaxException, IOException { + + Gson gson = new Gson(); + OauthToken oathToken = + gson.fromJson(getResourceFileAsString("rest_v1/oauth_token_0.json"), OauthToken.class); + + assertTrue(oathToken.isSuccess()); + assertEquals("[0] success", oathToken.toString()); + assertEquals("ABC123", oathToken.getData().getAccess_token()); + } + + @Test + void testVehicleList() throws JsonSyntaxException, IOException { + + Gson gson = new Gson(); + VehicleList vinList = + gson.fromJson(getResourceFileAsString("rest_v1/vehicle_list_0.json"), VehicleList.class); + + assertTrue(vinList.isSuccess()); + assertEquals("[0] success", vinList.toString()); + assertEquals(1, vinList.getData().getVinList().length); + assertEquals("ABCD1234567890123", vinList.getData().getVinList()[0].getVin()); + assertEquals("MG", vinList.getData().getVinList()[0].getBrandName()); + assertEquals("MG5 Electric", vinList.getData().getVinList()[0].getModelName()); + } + + static String getResourceFileAsString(String fileName) throws IOException { + ClassLoader classLoader = ClassLoader.getSystemClassLoader(); + try (InputStream is = classLoader.getResourceAsStream(fileName)) { + if (is == null) return null; + try (InputStreamReader isr = new InputStreamReader(is); + BufferedReader reader = new BufferedReader(isr)) { + return reader.lines().collect(Collectors.joining(System.lineSeparator())); + } + } + } +} diff --git a/saic-java-rest-client/src/test/java/net/heberling/ismart/java/rest/TestSaicApiClient.java b/saic-java-rest-client/src/test/java/net/heberling/ismart/java/rest/TestSaicApiClient.java new file mode 100644 index 0000000..918b394 --- /dev/null +++ b/saic-java-rest-client/src/test/java/net/heberling/ismart/java/rest/TestSaicApiClient.java @@ -0,0 +1,61 @@ +package net.heberling.ismart.java.rest; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import net.heberling.ismart.java.rest.api.v1.OauthToken; +import net.heberling.ismart.java.rest.api.v1.VehicleList; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Doug Culnane + */ +public class TestSaicApiClient { + + private final Logger logger = LoggerFactory.getLogger(TestSaicApiClient.class); + + @Test + public void testClient() throws Exception { + + // Set the environment variables to run the test against the API + String username = System.getenv("SAIC_USERNAME"); + String password = System.getenv("SAIC_PASSWORD"); + if (username == null || password == null) { + logger.warn( + "To activate SaicApiClient tests set the SAIC_USERNAME and SAIC_PASSWORD environment" + + " variables."); + return; + } + + HttpClient httpClient = + new HttpClient(new HttpClientTransportOverHTTP(), new SslContextFactory(true)); + httpClient.start(); + + SaicApiClient client = new SaicApiClient(httpClient); + OauthToken oauthToken = client.getOauthToken(username, password, "EN"); + + String token = oauthToken.getData().getAccess_token(); + VehicleList vehicleList = client.getVehicleList(token); + String vin = vehicleList.getData().getVinList()[0].getVin(); + + assertTrue(client.getVehicleStatus(token, vin).isSuccess()); + assertTrue(client.getVehicleStatisticsBasicInfo(token, vin).isSuccess()); + assertTrue(client.getVehicleChargingMgmtData(token, vin).isSuccess()); + assertTrue(client.getMessageNotificationList(token).isSuccess()); + + // Not much useful info + // assertTrue(client.getVehicleGeoFence(token, vin).isSuccess()); + // assertTrue(client.getVehicleStatisticsBasicInfo(token, vin).isSuccess()); + + // Failing + // assertTrue(client.getVehicleInfo(token, vin).isSuccess()); + // assertTrue(client.getVehicleStatusClassified(token, vin).isSuccess()); + // assertTrue(client.getMessageList(token, vin).isSuccess()); + // assertTrue(client.getVehicleCcInfo(token, vin).isSuccess()); + // assertTrue(client.getVehicleLocation(token, vin).isSuccess()); + } +}