[smartics : charger]

To apply some smart logic for getting excess energy into the car I need to set some data on the charger. Like I wrote last time, since I own an go-e charger I will build a service to access its API. I will keep it simple and just add methods to read the chargers current parameters as well as to set some of them. This, together with the previous implementations allows us to develop a smart logic next.


Let’s start on the lowest part: accessing the charger with a repository. I have the hard coded IP in there as there is still no possibility to adapt the settings just like with the inverter. It also looks quite similar as the API works in a similar REST fashion. Thus I’m using the Jackson object mapper here as well. The most important think is here how the values are set: by adding a key-value pair as a GET parameter. That is a little atypical. As you can already see, I am currently only interested in setting the ampere number, the color for charging and the enabling or disabling state of the charger. More will definitely follow later.

@Repository
public class ChargerRepositoryImpl implements ChargerRepository {
	private static final Logger LOG = LoggerFactory.getLogger(ChargerRepositoryImpl.class);
	private static final String CHARGER_IP = "http://192.168.1.200";
	private static final String CHARGER_STATUS = "/status";
	private static final String CHARGER_SET = "/mqtt?";
	private static final String CHARGER_QUERY = "payload=";

	@Autowired
	private ObjectMapper mapper;

	@Override
	public ChargerStatusDto getStatusData() {
    	JsonNode rootNode;
		try {
			rootNode = mapper.readTree(new URL(CHARGER_IP + CHARGER_STATUS));
			return ChargerStatusMapper.convertToDto(rootNode);
		} catch (IOException e) {
			LOG.error("getStatusData Exception on parsing json data {}", e.getMessage());
		}
		return new ChargerStatusDto(); 
	}

	@Override
	public boolean setAmpere(final String ampere) {
		JsonNode rootNode;
    	try {
    		final String query = CHARGER_IP + CHARGER_SET + CHARGER_QUERY + "amp=" + ampere;
    		LOG.info("GET: {}", query);
    		final InputStream response = new URL(query).openStream();
    		rootNode = mapper.readTree(response);

    		ChargerStatusDto result = ChargerStatusMapper.convertToDto(rootNode);
    		if (result.getAmpere().compareTo(Integer.valueOf(ampere)) == 0) {
    			return true;
    		}
    	} catch (IOException e) {
    		LOG.error("setStatusData Exception on parsing json data {}", e.getMessage());
    	}
		return false;
	}

	@Override
	public boolean setColorCharging(final String color) {
		JsonNode rootNode;
    	try {
    		final String query = CHARGER_IP + CHARGER_SET + CHARGER_QUERY + "cch=" + color;
    		LOG.info("GET: {}", query);
    		final InputStream response = new URL(query).openStream();
    		rootNode = mapper.readTree(response);

    		ChargerStatusDto result = ChargerStatusMapper.convertToDto(rootNode);
    		if (result.getColorCharging().compareTo(Integer.valueOf(color)) == 0) {
    			return true;
    		}
    	} catch (IOException e) {
    		LOG.error("setStatusData Exception on parsing json data {}", e.getMessage());
    	}
		return false;
	}

	@Override
	public boolean setAllowCharging(Boolean allowCharging) {
		JsonNode rootNode;
    	try {
    		final String query = CHARGER_IP + CHARGER_SET + CHARGER_QUERY + "alw=" + (allowCharging.booleanValue() ? "1" : "0");
    		LOG.info("GET: {}", query);
    		final InputStream response = new URL(query).openStream();
    		rootNode = mapper.readTree(response);

    		ChargerStatusDto result = ChargerStatusMapper.convertToDto(rootNode);
    		if (result.getAllowCharging().compareTo(allowCharging) == 0) {
    			return true;
    		}
    	} catch (IOException e) {
    		LOG.error("setStatusData Exception on parsing json data {}", e.getMessage());
    	}
		return false;
	}
}

The second important thing is already the mapper I need for converting the JSON I get from the charger. This is similar to the inverter mappers of course. Here we need to read through the API descriptions and figure out what is where and which values we actually want to map and use in our DTO. I need to note that the go-e charger will respond with deca watt hours and some other values that will need conversion later.

@Component
public class ChargerStatusMapper {
	private static final Logger LOG = LoggerFactory.getLogger(ChargerStatusMapper.class);

	public static ChargerStatusDto convertToDto(JsonNode rootNode) {
		Instant currentTime = getInstant(rootNode.get("tme").asText());
		Integer connectionStatus = getInteger(rootNode.get("car").asText());
		Integer temperature = getInteger(rootNode.get("tmp").asText());
		Integer maxAmpere = getInteger(rootNode.get("ama").asText());
		Integer loadedMkWhTotal = getInteger(rootNode.get("eto").asText());
		ChargerStatusDto status = new ChargerStatusDto(currentTime, ChargerStatus.getByCode(connectionStatus),
				temperature, maxAmpere, loadedMkWhTotal);

		status.setAllowCharging(getBoolean(rootNode.get("alw").asText()));
		status.setAmpere(getInteger(rootNode.get("amp").asText()));
		status.setColorCharging(getInteger(rootNode.get("cch").asText()));
		status.setColorIdle(getInteger(rootNode.get("cfi").asText()));

		status.setAutoStop(getBoolean(rootNode.get("stp").asText()));
		status.setAutoStopMkWh(getInteger(rootNode.get("dwo").asText()));
		status.setLoadedDWh(getInteger(rootNode.get("dws").asText()));

    	return status;
	}

	private static Instant getInstant(String text) {
		if (text.equals("")) {
			return null;
		}
		try {
			// parse time "ddmmyyhhmm"
			return InverterDateTimeFormater.getInstantForDMYHMDateTime(text);
		} catch (ParseException e) {
			LOG.error("ChargerStatusMapper dateTime could not be parsed: {}", text);
			return null;
		}
	}

	private static Integer getInteger(String text) {
		if (text.equals("")) {
			return null;
		}
		return new Integer(text);
	}

	private static Boolean getBoolean(String text) {
		if (text.equals("")) {
			// since null for Boolean is kind of a code smell
			return false;
		}
		return !text.contentEquals("0");
	}
}

What else is needed besides the data transfer object shown below (setters and getters are missing for compactness) and an practical enum for the charger/car status?

public class ChargerStatusDto {

	private Instant currentTime;
	private ChargerStatus connectionStatus;
	private Integer temperature;
	private Integer maxAmpere;
	private Integer loadedMkWhTotal;

	private Boolean allowCharging;
	private Integer ampere;
	private Integer colorCharging;
	private Integer colorIdle;

	private Boolean autoStop;
	private Integer autoStopMkWh;
	private Integer loadedDWh;


	public ChargerStatusDto() {}

	public ChargerStatusDto(Instant currentTime,
							ChargerStatus connectionStatus,
							Integer temperature,
							Integer maxAmpere,
							Integer loadedMkWhTotal) {
		this.currentTime = currentTime;
		this.connectionStatus = connectionStatus;
		this.temperature = temperature;
		this.maxAmpere = maxAmpere;
		this.loadedMkWhTotal = loadedMkWhTotal;
	}
}
public enum ChargerStatus {
	UNDEFINED(0),
	READY(1),
	LOADING(2),
	WAITING_FOR_DEVICE(3),
	LOADING_FINISHED(4);

	private final int code;

	ChargerStatus(int code) {
		this.code = code;
	}

	public int getCode() {
		return code;
	}

	public static ChargerStatus getByCode(Integer code){
		if (code == null || code == 0) {
			return ChargerStatus.UNDEFINED;
		}
	    for(ChargerStatus state : values()){
	        if(state.getCode() == code){
	            return state;
	        }
	    }
	    return null;
	}
}

Correct, for now: a service and a controller to actually trigger the call for parameters on the charger. That’s basically it. I skipped the interface of the repository and small utility methods like the DateTime formatter for compactness. They are easily found in my Github repository for smartics.

@Service
public class ChargerService {
	private static final Logger LOG = LoggerFactory.getLogger(ChargerService.class);

	@Autowired
	private ChargerRepository chargerRepository;

	public ChargerStatusDto getStatusData() {
		ChargerStatusDto status = chargerRepository.getStatusData();
		LOG.debug("charger queried, returns time: {}, ampere {}, connectionStatus {}",
				status.getCurrentTime(), status.getAmpere(), status.getConnectionStatus());
		return status;
	}

	public boolean setAmpere(final String ampere) {
		if (ampere == null || ampere.isEmpty()) {
			return false;
		}
		return chargerRepository.setAmpere(ampere);
	}

	public boolean setColorCharging(final String color) {
		if (color == null || color.isEmpty()) {
			return false;
		}
		return chargerRepository.setColorCharging(color);
	}

	public boolean setAllowCharging(final String allowCharging) {
		if (allowCharging == null || allowCharging.isEmpty()) {
			return false;
		}
		return chargerRepository.setAllowCharging(Boolean.valueOf(allowCharging));
	}
}
@RestController
public class ChargerController {
	@Autowired
	private ChargerService chargerService;

	/**
	 * read current status from charger.
	 * @return {@link ChargerStatusDto}
	 */
	@GetMapping("/api/chargerstatus")
	public ChargerStatusDto getChargerStatus() {
		return chargerService.getStatusData();
	}

	/**
	 * set parameter of charger with specified name and value in body.
	 * @param name
	 * @param value
	 * @return true if it was set correctly, false otherwise
	 */
	@PutMapping("api/charger/{name}")
	public boolean setParameter(@PathVariable String name, @RequestBody String value) {
		if (name.contentEquals("ampere")) {
			return chargerService.setAmpere(value);
		} else if (name.contentEquals("colorCharging")) {
			return chargerService.setColorCharging(value);
		} else if (name.contentEquals("allowCharging")) {
			return chargerService.setAllowCharging(value);
		}
		return false;
	}
}

Both service and REST controller are rather straightforward. I will iterate and improve the code as I progress and extend the whole application. This is very important for almost any kind of software. With the controller I have the option to query my pi for the charger status:

http://192.168.1.xxx:8080/api/chargerstatus

Which will return my DTO defined before with values, for example:

{
  "currentTime": "2020-01-12T21:04:00Z",
  "connectionStatus": "READY",
  "temperature": 15,
  "maxAmpere": 32,
  "loadedMkWhTotal": 6000,
  "allowCharging": true,
  "ampere": 8,
  "colorCharging": 65535,
  "colorIdle": 65280,
  "autoStop": false,
  "autoStopMkWh": 0,
  "loadedDWh": 0
}

With CURL I can also set parameters like in the following example. The return value will only be true on success or false in any error case, when the value was not set. The following command will set the charger to 10 ampere output.

curl -X PUT http://192.168.1.xxx:8080/api/charger/ampere -H 'cache-control: no-cache' -H 'content-type: application/json' -d '10'

That’s basically it! I can start to think about some smartness now. I will integrate that next in my scheduler and take a few states like current (within the last minutes) energy produced, used and maybe time of day into account. I might also start to think about a story for charger data (charged, from+until, percentage of solar power). Then, before I can go hacking the on-board diagnostics of the car, I will also write an interface to see some statistics and change charger settings.

— Raphael

1 thought on “[smartics : charger]”

Leave a comment