Laadpunt: Difference between revisions
No edit summary |
|||
Line 117: | Line 117: | ||
* Tussen toestanden geauthenticeerd en ongeauthenticeerd schakelen door aanbieden iButton | * Tussen toestanden geauthenticeerd en ongeauthenticeerd schakelen door aanbieden iButton | ||
* Toestand vasthouden | * Toestand vasthouden | ||
* Gebruikersnaam deelnemer aan Raspberry Pi communiceren (voor vastleggen verbruik per gebruiker, IRC updates) | * Gebruikersnaam deelnemer aan Raspberry Pi communiceren (voor vastleggen verbruik per gebruiker, IRC updates) | ||
* Override voor schakelen geauthenticeerd en ongeauthenticeerd via het netwerk, ofwel via Raspberry Pi | * Override voor schakelen geauthenticeerd en ongeauthenticeerd via het netwerk, ofwel via Raspberry Pi | ||
===== Arduino code ===== | |||
Dit is begonnen als Doorduino code, maar is inmiddels wat over-engineered. | |||
<pre> | |||
#include <OneWire.h> | |||
// Board layout (Doorduino) | |||
#define PIN_LED_GREEN 13 | |||
#define PIN_LED_RED 12 | |||
#define PIN_RELAY 11 | |||
#define PIN_1WIRE 10 | |||
#define PIN_BUTTON 9 | |||
#define OFF 0 // Undefined state (or off/broken, obviously) | |||
#define GREEN 1 // Charging available | |||
#define RED 2 // Stuff b0rked | |||
#define YELLOW (GREEN | RED) // Yellow means ready for auth (same as Doorduino) | |||
#define INIT_DELAY 500 // Delay after all setup operations | |||
#define LED_BLINK_INTERVAL 2000 // Be nice, blink to people/robots/ghosts/other things passing by | |||
#define LED_BLINK_TIME 120 // Each fancy led blink last this much miliseconds | |||
#define BUTTON_INTERVAL_TIME 3000 // After an iButton has been read, no read operation for this much miliseconds | |||
#define ABANDONED_TIME 30000 // Max interval between serial messages before Arduino considers the managing device down | |||
#define ABANDONED_MSG_INTERVAL 5000 // The interval between each message send over serial, when in the ABANDONED state | |||
OneWire ds(PIN_1WIRE); // Dallas 1-Wire bus | |||
enum Command { | |||
NONE, // No command received | |||
ENABLE, // Manager says user/customer is authorized | |||
DISABLE, // Manager says user/customer not authorized | |||
PING // Sign of life | |||
}; | |||
static enum State { | |||
ABANDONED, // Arduino has not had contact with the managing device for too long, or is yet to receive the first message | |||
ENABLED, // Charging is available to the user/customer | |||
DISABLED // No charging possible, ready for a new customer | |||
} state; // State the system is in | |||
// These values are in miliseconds since start of program. Ulongs are 32 bits on arduino, which gets you | |||
// up to 4.294.967.295 msec or just over 49 and a half days. So values that affect state need some guards for millis() overflowing back to zero | |||
static unsigned long lastblink = 0; // Last time we did a nice blinky on the led | |||
static unsigned long lastread = 0; // Last time an iButton was read | |||
static unsigned long keepalive = 0; // Last time a sign of life was received from the managing device (affects device state!) | |||
static unsigned long lastmsg = 0; // Last time we have send a message to the managing device (currently used to limit the abandoned message over serial rate) | |||
void setup () { | |||
led(YELLOW); // Led test (if the reader is green or red during power-up, the other color is broken) | |||
Serial.begin(115200); // Same as Doorduino | |||
Serial.println("INIT"); // Doorduino says "RESET", Chargeduino says "INIT" | |||
pinMode(PIN_LED_GREEN, OUTPUT); | |||
pinMode(PIN_LED_RED, OUTPUT); | |||
pinMode(PIN_RELAY, OUTPUT); | |||
pinMode(PIN_BUTTON, INPUT); // Unused at this moment. TODO: Make a car explode on push | |||
state = ABANDONED; // When system initializes, nothing is possible until managing device has shown a sign of life | |||
delay(INIT_DELAY); // To make the led test effective (init is very fast) | |||
} | |||
void loop () { | |||
// Overflow protections regarding timing. Since millis() overflows back to zero | |||
// somewhere in the 49th day, some guard are required to prevent nutty behaviour | |||
if (millis() < keepalive) { | |||
keepalive = 0; // Overflow detected, reset keepalive | |||
} | |||
// The other timing-related values are not affecting state. If anything, a weird led flicker is kinda interesting so yeah whatever | |||
// Check for messages from the managing device | |||
Command mancmd = readSerial(); | |||
switch (mancmd) { | |||
case ENABLE: { | |||
state = ENABLED; | |||
keepalive = millis(); | |||
break; | |||
} | |||
case DISABLE: { | |||
state = DISABLED; | |||
keepalive = millis(); | |||
break; | |||
} | |||
case PING: { | |||
keepalive = millis(); | |||
break; | |||
} | |||
} | |||
// If there has been no input from the managing device, we consider ourselves abandoned. | |||
// First compare loops backwards to near max value first msecs of program operation, hence the second one | |||
if (millis() - ABANDONED_TIME > keepalive && millis() > ABANDONED_TIME) { | |||
state = ABANDONED; | |||
} | |||
// Read the one wire bus, see if there's a button | |||
if (state != ABANDONED) { // We only care about iButtons if there's a managing device present | |||
// First compare loops backwards to near max value first msecs of program operation, hence the second one | |||
if (millis() - BUTTON_INTERVAL_TIME > lastread && millis() > BUTTON_INTERVAL_TIME) { | |||
if (readButton()) { | |||
lastread = millis(); // A button was successfully read | |||
} | |||
} | |||
} | |||
// Allow or not allow charging, depending on what input has been given before | |||
switch (state) { | |||
case ENABLED: { | |||
digitalWrite(PIN_RELAY, HIGH); // By enabling the relay, the SAE J1772 CP-pin is enabled | |||
led(GREEN); | |||
break; | |||
} | |||
case DISABLED: { | |||
digitalWrite(PIN_RELAY, LOW); // By disabling the relay, the SAE J1772 CP-pin connection is cut | |||
led(YELLOW); | |||
break; | |||
} | |||
case ABANDONED: { | |||
digitalWrite(PIN_RELAY, LOW); // By disabling the relay, the SAE J1772 CP-pin connection is cut | |||
led(RED); | |||
if (millis() - ABANDONED_MSG_INTERVAL > lastmsg && millis() > ABANDONED_MSG_INTERVAL) { | |||
Serial.println("ABANDONED"); | |||
lastmsg = millis(); | |||
} | |||
break; | |||
} | |||
} | |||
} | |||
void led (byte color) { | |||
if (millis() - LED_BLINK_INTERVAL > lastblink && millis() > LED_BLINK_INTERVAL) { | |||
digitalWrite(PIN_LED_GREEN, 0); | |||
digitalWrite(PIN_LED_RED, 0); | |||
lastblink = millis(); | |||
} | |||
if (millis() - LED_BLINK_TIME > lastblink) { | |||
digitalWrite(PIN_LED_GREEN, color & GREEN); | |||
digitalWrite(PIN_LED_RED, color & RED); | |||
} | |||
} | |||
bool readButton() { | |||
byte id[8]; // ID of the iButton will be written into this variable by ds.search() | |||
if (ds.reset()) { // After a reset pulse, if one or more slave devices are on the bus, reset returns nonzero | |||
ds.reset_search(); // 1-Wire bus can have multiple slave devices. Reset next use of search() to start/first position | |||
if (ds.search(id)) { // Do a search until something is found, write the ID into id (stateful; next call returns next device). Returns nonzero if a new ID was written to id | |||
if (OneWire::crc8(id, 7) != id[7]) { | |||
return false; // If checksum fails, return | |||
} | |||
led(OFF); | |||
Serial.print("<"); | |||
for (byte i = 0; i < 8; i++) { // Dump the found ID to serial out | |||
if (id[i] < 16) Serial.print("0"); | |||
Serial.print(id[i], HEX); | |||
} | |||
Serial.println(">"); | |||
return true; | |||
} | |||
} | |||
return false; | |||
} | |||
Command readSerial() { | |||
while (Serial.available()) { | |||
char c = Serial.read(); | |||
if (c == 'E') { // Enable charging | |||
Serial.println("ENABLED"); | |||
return ENABLE; | |||
} | |||
else if (c == 'D') { | |||
Serial.println("DISABLED"); | |||
return DISABLE; | |||
} | |||
else if (c == 'P') { // Yo still there bro? | |||
Serial.println("PONG"); | |||
return PING; // In this case, it means exactly the opposite | |||
} | |||
} | |||
return NONE; | |||
} | |||
</pre> | |||
== Reverse-engineering == | == Reverse-engineering == |
Revision as of 00:16, 19 August 2018
Project Laadpunt | |
---|---|
Een laadpunt voor elektrische auto's @Revspace | |
Status | In progress |
Contact | merethan |
Last Update | 2018-08-19 |
Er komen meer en meer elektrische auto's op de Nederlandse wegen. Een groot nadeel van deze voertuigen is dat accu's in vergelijking met een brandstoftank nog altijd wat beperkt zijn in hoeveel energie ze kunnen vasthouden. Om lekker rond te kunnen karren heb je dus wat vaker toegang nodig tot een stopcontact dan dat je een brandstof auto moet tanken. Niet alleen vaker, maar ook langer, want het laden duurt even.
Omdat het laden even duurt is het daarom handig een stopcontact te hebben op de plek waar je heen gaat. Terwijl je ergens bent kan je auto dan opladen. Revspace is zo'n plek waar ik regelmatig ben, maar tot nu toe geen "echt" laadpunt was.
Sinds september 2016 heb ik een Renault Twizy. Daar heb je eigenlijk geen "echt" laadpunt voor nodig, want hij laad slechts met 230VAC 10A (enkele fase). Er zit dan ook een doodnormale Schuko stekker aan, en niet de in Europa voor auto's bekende Mennekes Type 2. Wanneer ik wat laag aan de acculading zat deed ik mijn Twizy dan ook via een normaal (geschikt) verlengsnoer opladen bij Revspace. Vanaf het begin waren er al geluiden dat dit problemen kon gaan geven omdat het niet echt als "fair use" te verkopen valt aan de partij waarvan Revspace het pand van huurt. Maar gezien de Twizy niet een "echte" auto was en ik het niet heel vaak deed, werd een oogje toe geknepen.
Sinds ergens begin 2018 heeft mux echter ook een elektrische kar, namelijk een 1st gen. Nissan Leaf (binnenkort met extra 18650's uitgerust, I kid you not). Toen die ook kwam laden was de pand eigenaar "not amused". Nu er meer en ook grotere rijdende accu's Revspace aandoen, hebben we dus een "echt" laadpunt nodig, die stroom per "klant" afrekent; gezien het significante stroomverbruik tijdens het laden van voertuigen kan dit niet meer onder de elektra fair use policy van de rest van de space geschaard worden.
Tijdelijke administratie kWh's
Totdat we een werkend laadpunt met automatische afrekening hebben, houden we hier bij hoeveel er geladen wordt.
Mux:
- ?? Februari: 12 kWh
- Wo 11 april: 3 kWh
- Ma 30 april: 16 kWh
- Di 1 mei: 11 kWh
Het laadpunt
Momenteel zijn er twee laadpunten in bouw. Mux heeft er voor gekozen allerhande spulletjes uit China over te laten komen, en uit componenten zelf iets te fabriceren. Ik (merethan) heb er voor gekozen naar een tweede hands of kapotte op zoek te gaan, en die net zo lang te hacken tot het doet wat ik wil. Ik heb wat rond gezocht en gemaild, waarna ik reactie van laadpunt.nl heb gekregen. Die hadden nog een ingeruilde liggen, die ik voor een relatief zacht prijsje mocht overnemen, mits ik er ook echt wat boeiends mee ging doen. Dank aan laadpunt.nl daarvoor.
Keba P20
Het apparaat betreft een Keba KeContact P20. Hij heeft reeds ruim 2 jaar dienst gedaan bij de vorige eigenaar. De vorige eigenaar heeft 'em ingeruild voor een nieuwer en geavanceerder model, naar verluid omdat hij/zij meer management opties wilde.
Keba AG
Keba AG. is een Oostenrijks bedrijf wat actief is in meerdere markten. Voor ze laadpalen zijn gaan maken deden ze al allerhande andere automatisering, waaronder machines voor plastic gieten, metaalbewerking, kluisjessystemen en bankautomaten (flappentappen & contant deposit).
Specs & features
Het volledige modelnummer is: KC-P20-ES240010-00R. De installatiehandleiding bevat een handig tabelletje om dat te decoderen. Het komt neer op het volgende: KeContact, model P20, Europese uitvoering, met een Socket (ipv vaste kabel), 2 = Mennekes Type-2, 4 = 32A versie, 00 = 0 meter kabel (duh..), 1 = b-series model, 0 = "Contactor electrics" (wut??), 00R = met RFID uitgevoerd.
Het betreft dus, gezien het 3-fase 230V op 32A is, een 22kW laadpunt.
Installatie
Het apparaat moet upstream afgezekerd worden met een automaat die bij de specs van de bekabeling past en een eigen aardlekschakelaar. Ook moet het maximale laadvermogen dmv DIP-switches ingesteld worden, zodat de paal dit kan communiceren aan de auto.
Interfaces
Het apparaat heeft meerdere interfaces via welke het interactie heeft met de rest van de wereld.
Mennekes Type 2 plug met SAE J1772 signallering
!! Mux moet deze sectie nog fact-checken en aanvullen !!
De meest elementaire interface aan het laadpunt is het stopcontact voor de auto: De Mennekes Type 2 connector. Deze bevat naast een PE, N, L1, L2 en L3 (de pinnen die je kent van krachtstroom) ook nog de pinnen PP (proximity pilot) en CP (control pilot). Via PP en CP communiceren de auto, de kabel en het laadsysteem hoeveel vermogen er getrokken mag worden. De logica hierachter is echt heel low-tech en niet uniek voor deze plug. Het "protocol" is namelijk overgenomen van een andere standaard: De Japans/Amerikaanse SAE J1772 plug.
Via de PP pin laat de kabel (de stekker eigenlijk) weten voor hoeveel ampère de kabel geschikt is. Dit werkt zeer low-tech: Een bepaalde weerstand tussen de +12V PP pin en de PE pin codeert van welke klasse de kabel is. Via deze pin kan zowel de auto als het laadstation dus ook detecteren wanneer er een kabel ingeplugd is. De PP pin is ook korter dan de andere pinnen, zodat het laadstation of de auto de stroom kan afschakelen voordat de rest van de pinnen contact verliezen (dit om vonken te voorkomen). De PP pin is niet van de ene naar de andere kant van de kabel verbonden.
De CP pin is wel verbonden door de kabel heen, wat de auto en het laadpunt in staat stelt om te communiceren met elkaar. Again, dit werkt zeer low-tech: Het laadpunt zet een square-wave van ±12V (-12V tot +12V) op de CP pin, met een frequentie van 1Khz. Als de PWM 1Khz. is betekent dat dat de auto "vol vermogen" mag trekken. Hoe lager de frequentie, hoe minder amps de auto mag trekken. Online zijn tabellen te vinden van welke PWM cycle hoeveel ampères betekent.
De CP pin is aan de auto kant verbonden met de PE pin via een weerstand en een diode. Omdat maar de helft van de ±12V square-wave afgevlakt wordt (diode) weet het laadstation dat het snoer ook echt in een auto zit en niet in de regen ligt. De weerstand die de auto tussen CP en PE plaatst codeert verschillende "staten" waarin de auto zich bevind. Zo is er bijvoorbeeld een specifieke weerstand waarmee de auto aan het laadsation aangeeft dat hij wil gaan laden. Online zijn tabellen te vinden met welke weerstand welke staat codeert.
Meer diepgaande uitleg en tabellen van coderingen is op Wikipedia te vinden.
Voor de duidelijkheid: Het laadpunt is enkel een circuit breaker. Het laadpunt doet niets in de trand van power duty cycle regeling. Als de auto meer amps trekt dan het door het laadpunt opgegeven maximum, gaat de stroom gewoon uit.
GreenPhy protocol
Het laadstation ondersteunt ook PLC (Power Line Communication) met de auto dmv het GreenPhy protocol. Voor wat ik er nu van begrijp stelt dat de auto in staat om via de AC kabels op 10mbit te communiceren met het LAN waar het laadpunt aan vast zit. Zeer vergelijkbaar met "power line ethernet" wat je binnen je huis kunt doen, als je te lui bent om kabels te trekken.
RFID
Bijna alle laadstations hebben een methode voor authorisatie/authenticatie. Het authorisatie/authenticatiesysteem van het door mij gekochte model werkt op basis van RFID tokens. RFID functionaliteit wordt verzorgd door een ddm Hopt+Schuler 881 module (datasheet). Deze reader is compatible met Mifare/ISO14443/ISO15693 tokens. Voor zover ik het systeem nu begrijp blaft de module gewoon het serienummer van de gepresenteerde token over een RS232 TTL lijn heen en dat is dan de sleutel.
Enable-input
Binnenin het laadstation zit een enable-input (terminal X1) voor interfacing met een ander systeem. Door deze terminal kort te sluiten is het systeem "enabled". Hierin slaat "enabled" niet op authorisatie: Deze input schakelt het laden aan of uit, maar voorkomt aankoppelen niet. Dus als de enable-input onderbroken (uit) is, en iemand koppelt een kabel/auto aan, dan wordt die wel gelocked en begint initialisatie van het laadproces. Laden (stroom leveren) begint echter pas als je X1 kortsluit. (Dat zou je bijvoorbeeld kunnen gebruiken icm een zonnepanelen systeem, zodat je enkel overcapaciteit in je auto laad.) De enable-input is dus geen access control systeem of te gebruiken om een access control systeem op aan te sluiten.
Ethernet
De Keba P20 heeft een ethernetpoortje (terminal X3). De IP instellingen zijn te regelen met de DIP-switches. Momenteel gebruikt het apparaat 192.168.25.11 als adres.
UDP input
Keba laadpunten ondersteunen volgens de UDP programmers manual een setje commando's die je via ethernet kunt sturen. Dit zijn simpele tekst-gebaseerde commando's over UDP, op poort 7090. Open een shell met 'netcat -l -p 7090 -u -b 192.168.25.11 7090' om te luisteren, ga in een andere shell commando's sturen met 'echo -n "[command]" | netcat -q 0 -u 192.168.25.11 7090', en je bent in business.
De door mij gekochte versie is echter het instapmodel, en ondersteunt daarom vrijwel niks van wat in de UDP manual van Keba staat. Dit is waarschijnlijk slechts een upselling scheme wat enkel in de software verankerd is, maar voor nu even waar we het mee moeten doen. De enige commando's waar mijn model zover op gereageerd heeft is i, waarop hij antwoord met "Firmware: KEBA P20 v 2.5a3 (160613-061001)", en report 1, waarop je iets meer info terug krijgt (ook serienummer & modelnummer). En dat was het.
UDP output
De ethernetpoort is niet geheel waardeloos, want hij doet wel nuttige UDP broadcasts. Bij elke status verandering doet hij een bijpassende broadcast met de veranderde waarden. Denk aan de toestand van het apparaat (wachten/aankoppelen/laden etc.), het koppelen/ontkoppelen van de kabel en/of auto, wanneer er authorisatie is (RFID/UDP), enable input (X1) open/dicht, en een verandering in hoeveelheid geleverde energie. Zie de UDP programmers guide van Keba voor een overzicht & uitleg.
Hij gaat overigens pas broadcasts geven nadat je even tegen hem gepraat heb. Het is net een dronkenlap in een bar: Je moet even één vraag stellen maar daarna houd hij niet meer op met praten.
HTTP
Op poort 80 zit een webserver, die een pagina serveert waar wat statistieken op staan vergelijkbaar met wat via UDP broadcasts gepubliceert wordt.
OCPP
Het apparaat lijkt iets van "communication hub mode" ofzo met een KeContact C10 communication hub te kunnen via OCPP. Geen idee wat het precies is, maar wellicht het onderzoeken waard. Het lijkt iets te zijn waarbij je "intelligent" energie kunt verdelen, indien je een systeem heb met meerdere van deze laadstations.
Interfacing met de space
Wat ik wil maken is dat elke deelnemer zich met zijn/haar Secure_iButton kan authenticeren op het laadstation en dat de kosten voor de geleverde energie worden afgerekend via de bar. Er moet dus op een manier een iButton lezer in, en een interface naar de bar gemaakt worden. Ook lijkt het mij leuk om status updates te doen op het IRC kanaal, vergelijkbaar met deuren die geopend worden.
Mogelijke mods om iButton lezer te koppelen
Authenticatie bij de door mij gekochte uitvoering van het laadstation werkt door RFID-tags in te programmeren en die te gebruiken als sleutel. RFID wordt verzorgd door een losse module, die door een eigen stuk hardware te emuleren is. Hij zit via RS232 TTL vast aan de rest en heeft een manual, dus met wat logic analyzer magic, RTFM en blijven proberen moet dat wel te gebruiken zijn als aansluit interface voor eigen access control (stukje hardware wat iButtons kan lezen).
Keba palen kunnen hetgeen wat met RFID kaartjes kan ook via de ethernetpoort, als je genoeg geld neerlegt bij Keba. Het door mij gekochte model is echter de goedkope versie, die deze features "niet heeft" (ofwel het staat uit in de software). De afwezigheid van deze features is waarschijnlijk de reden geweest dat de vorige eigenaar dit laadstation heeft ingeruild voor een nieuwere/duurdere.
Zoals eerder genoemd bij de Enable-input sectie: Deze is niet te gebruiken voor authorisatie. Deze input is om het laden tijdelijk te onderbreken en weer verder te gaan met laden wanneer er weer zonlicht op de zonnepanelen valt.
Een andere mod die erg goed blijkt te werken is CP pin onderbreken. Wanneer de CP niet meer verbonden is met de PE pin stopt de software het laden en wordt de stekker ontgrendeld. Bij inpluggen wordt de stekker wel altijd vergrendeld, maar als er geen laadoperatie gestart wordt in ~30 sec. gaat hij alsnog weer los. Ook als de stekker al los is gegaan door de timeout kan alsnog een laadsessie gestart worden door de CP pin weer te verbinden (de stekker vergrendeld dan weer). Dit tezamen met een lege ACL en een relais lijkt de ideale hack om een eigen authorisatie/authenticatiesysteem in te bouwen.
Voor verbruiksstatistieken en status updates naar IRC zijn de UDP broadcasts op de ethernet poort bruikbaar. Voor de meest actuele verbruiksstand kan ook de output van de webserver gebruikt worden.
Mod hardware
Ik heb een gen 1. Pi model B, die op de ethernetpoort van de Keba kan. De Pi kan zo de statistieken van de energielevering opvragen (http get) en/of bijhouden (UDP luisteren). Wellicht is het mogelijk automatisch af te rekenen via de bar, zo niet dan wordt om de zoveel tijd handmatig voor iedereen afgerekend. Tevens moet de Pi ook updates naar IRC kunnen sturen. Voor het uitlezen van iButtons wordt gebruik gemaakt van een gemodificeerde Doorduino. Het onderbreken van de CP pin is prima te doen met een relais aangesloten op de Doorduino.
Raspberry Pi
Softwares? Hoe de boel te scripten danwel programmeren in een daemon?
iButton lezer
De Doorduino moet in staat zijn twee staten te hebben. Momenteel openen ze enkel een deur en gaan ze daarna weer terug naar "dicht". De software moet wat aangepast worden zodat ze een toestand vast houden tot dezelfde button weer contact maakt. Wellicht is het ook wenselijk dat de Doorduino weer terug kan van geauthenticeerd naar ongeauthenticeerd als de auto de laadoperatie onderbreekt (ook de auto kan de CP pin onderbreken; dit emuleren we feitelijk met de relais). En er moet een override via het netwerk zijn, om ellende op afstand te kunnen oplossen.
Puntsgewijs:
* Tussen toestanden geauthenticeerd en ongeauthenticeerd schakelen door aanbieden iButton * Toestand vasthouden * Gebruikersnaam deelnemer aan Raspberry Pi communiceren (voor vastleggen verbruik per gebruiker, IRC updates) * Override voor schakelen geauthenticeerd en ongeauthenticeerd via het netwerk, ofwel via Raspberry Pi
Arduino code
Dit is begonnen als Doorduino code, maar is inmiddels wat over-engineered.
#include <OneWire.h> // Board layout (Doorduino) #define PIN_LED_GREEN 13 #define PIN_LED_RED 12 #define PIN_RELAY 11 #define PIN_1WIRE 10 #define PIN_BUTTON 9 #define OFF 0 // Undefined state (or off/broken, obviously) #define GREEN 1 // Charging available #define RED 2 // Stuff b0rked #define YELLOW (GREEN | RED) // Yellow means ready for auth (same as Doorduino) #define INIT_DELAY 500 // Delay after all setup operations #define LED_BLINK_INTERVAL 2000 // Be nice, blink to people/robots/ghosts/other things passing by #define LED_BLINK_TIME 120 // Each fancy led blink last this much miliseconds #define BUTTON_INTERVAL_TIME 3000 // After an iButton has been read, no read operation for this much miliseconds #define ABANDONED_TIME 30000 // Max interval between serial messages before Arduino considers the managing device down #define ABANDONED_MSG_INTERVAL 5000 // The interval between each message send over serial, when in the ABANDONED state OneWire ds(PIN_1WIRE); // Dallas 1-Wire bus enum Command { NONE, // No command received ENABLE, // Manager says user/customer is authorized DISABLE, // Manager says user/customer not authorized PING // Sign of life }; static enum State { ABANDONED, // Arduino has not had contact with the managing device for too long, or is yet to receive the first message ENABLED, // Charging is available to the user/customer DISABLED // No charging possible, ready for a new customer } state; // State the system is in // These values are in miliseconds since start of program. Ulongs are 32 bits on arduino, which gets you // up to 4.294.967.295 msec or just over 49 and a half days. So values that affect state need some guards for millis() overflowing back to zero static unsigned long lastblink = 0; // Last time we did a nice blinky on the led static unsigned long lastread = 0; // Last time an iButton was read static unsigned long keepalive = 0; // Last time a sign of life was received from the managing device (affects device state!) static unsigned long lastmsg = 0; // Last time we have send a message to the managing device (currently used to limit the abandoned message over serial rate) void setup () { led(YELLOW); // Led test (if the reader is green or red during power-up, the other color is broken) Serial.begin(115200); // Same as Doorduino Serial.println("INIT"); // Doorduino says "RESET", Chargeduino says "INIT" pinMode(PIN_LED_GREEN, OUTPUT); pinMode(PIN_LED_RED, OUTPUT); pinMode(PIN_RELAY, OUTPUT); pinMode(PIN_BUTTON, INPUT); // Unused at this moment. TODO: Make a car explode on push state = ABANDONED; // When system initializes, nothing is possible until managing device has shown a sign of life delay(INIT_DELAY); // To make the led test effective (init is very fast) } void loop () { // Overflow protections regarding timing. Since millis() overflows back to zero // somewhere in the 49th day, some guard are required to prevent nutty behaviour if (millis() < keepalive) { keepalive = 0; // Overflow detected, reset keepalive } // The other timing-related values are not affecting state. If anything, a weird led flicker is kinda interesting so yeah whatever // Check for messages from the managing device Command mancmd = readSerial(); switch (mancmd) { case ENABLE: { state = ENABLED; keepalive = millis(); break; } case DISABLE: { state = DISABLED; keepalive = millis(); break; } case PING: { keepalive = millis(); break; } } // If there has been no input from the managing device, we consider ourselves abandoned. // First compare loops backwards to near max value first msecs of program operation, hence the second one if (millis() - ABANDONED_TIME > keepalive && millis() > ABANDONED_TIME) { state = ABANDONED; } // Read the one wire bus, see if there's a button if (state != ABANDONED) { // We only care about iButtons if there's a managing device present // First compare loops backwards to near max value first msecs of program operation, hence the second one if (millis() - BUTTON_INTERVAL_TIME > lastread && millis() > BUTTON_INTERVAL_TIME) { if (readButton()) { lastread = millis(); // A button was successfully read } } } // Allow or not allow charging, depending on what input has been given before switch (state) { case ENABLED: { digitalWrite(PIN_RELAY, HIGH); // By enabling the relay, the SAE J1772 CP-pin is enabled led(GREEN); break; } case DISABLED: { digitalWrite(PIN_RELAY, LOW); // By disabling the relay, the SAE J1772 CP-pin connection is cut led(YELLOW); break; } case ABANDONED: { digitalWrite(PIN_RELAY, LOW); // By disabling the relay, the SAE J1772 CP-pin connection is cut led(RED); if (millis() - ABANDONED_MSG_INTERVAL > lastmsg && millis() > ABANDONED_MSG_INTERVAL) { Serial.println("ABANDONED"); lastmsg = millis(); } break; } } } void led (byte color) { if (millis() - LED_BLINK_INTERVAL > lastblink && millis() > LED_BLINK_INTERVAL) { digitalWrite(PIN_LED_GREEN, 0); digitalWrite(PIN_LED_RED, 0); lastblink = millis(); } if (millis() - LED_BLINK_TIME > lastblink) { digitalWrite(PIN_LED_GREEN, color & GREEN); digitalWrite(PIN_LED_RED, color & RED); } } bool readButton() { byte id[8]; // ID of the iButton will be written into this variable by ds.search() if (ds.reset()) { // After a reset pulse, if one or more slave devices are on the bus, reset returns nonzero ds.reset_search(); // 1-Wire bus can have multiple slave devices. Reset next use of search() to start/first position if (ds.search(id)) { // Do a search until something is found, write the ID into id (stateful; next call returns next device). Returns nonzero if a new ID was written to id if (OneWire::crc8(id, 7) != id[7]) { return false; // If checksum fails, return } led(OFF); Serial.print("<"); for (byte i = 0; i < 8; i++) { // Dump the found ID to serial out if (id[i] < 16) Serial.print("0"); Serial.print(id[i], HEX); } Serial.println(">"); return true; } } return false; } Command readSerial() { while (Serial.available()) { char c = Serial.read(); if (c == 'E') { // Enable charging Serial.println("ENABLED"); return ENABLE; } else if (c == 'D') { Serial.println("DISABLED"); return DISABLE; } else if (c == 'P') { // Yo still there bro? Serial.println("PONG"); return PING; // In this case, it means exactly the opposite } } return NONE; }
Reverse-engineering
Initieel ligt de focus op het realiseren van een laadpunt. Later gaan we de hardware en software nog reverse-engineeren. Voor shits & giggles, en wie weet wat er allemaal nog mogelijk blijkt (hint: die UDP aansturingsfeatures zijn wel leuk om te hebben).
Paar losse flarden info:
- lwIP (lightweight IP) TCP/IP stack
- Contiki als OS?