Skip to content

Commit c9cbe78

Browse files
committed
add registration
1 parent 5fd7629 commit c9cbe78

File tree

9 files changed

+127
-24
lines changed

9 files changed

+127
-24
lines changed

README.md

+15-15
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Tested against a MY18 vehicle.
1515

1616
* MQTT proxy to Phev
1717
* Home Assistant discovery
18+
* Register client to car
1819
* Fetch battery, charge, door, light status
1920
* Set lights and charge enable
2021
* Near-instant response to commands
@@ -25,8 +26,6 @@ Also includes some debugging utilities.
2526
## Requirements
2627

2728
* Go compiler
28-
* To connect, a previously registered connection to a phone/tablet.
29-
* This library doesnt yet support client registration.
3029

3130
## Licence, etc
3231

@@ -59,24 +58,25 @@ Contributions and PRs are welcome.
5958

6059
### Connecting to the vehicle.
6160

62-
#### Register a mobile device with official app
61+
#### Configure Wifi client on system running mqtt2phev
6362

64-
As the program does not (yet) support client registration, you will first need to
65-
register a phone/tablet to the car. Follow the [Mitsubishi instructions](https://www.mitsubishi-motors.com/en/products/outlander_phev/app/remote/)
66-
and register the phone app to the car. You will need the Wifi credentials provided
67-
with the car.
63+
On your computer running the phev2mqtt tools, configure a new Wifi connection to the
64+
car's SSID,
6865

69-
#### Obtain MAC address the app uses
66+
#### Register the client to the car
7067

71-
Next, find the MAC address of the client. On your phone/table, go to Wifi settings,
72-
search for the car SSID and find the MAC address used. On Android this will likely
73-
be a randomised address. Note this address down.
68+
Follow the [Mitsubishi instructions](https://www.mitsubishi-motors.com/en/products/outlander_phev/app/remote/)
69+
to find the Wifi credentials provided with the car.
7470

75-
#### Configure Wifi client on system running mqtt2phev
71+
Verify that your Wifi connection to the car is established - your local IP address
72+
should be 192.168.8.47.
7673

77-
On your computer running the phev2mqtt tools, configure a new Wifi connection to the
78-
car's SSID, and it's also essential to set the Wifi adapter mac address to the client
79-
MAC address you noted above. Poke around online for how to do this for your system.
74+
Follow the [Mitsubishi instructions](https://www.mitsubishi-motors.com/en/products/outlander_phev/app/remote/)
75+
and put the car into registration mode ("Setup Your Vehicle"). You may need to
76+
re-establish the Wifi connection.
77+
78+
Register by running `phev2mqtt client register` and you should shortly see a message
79+
indicating successful registration.
8080

8181
#### Testing the tool
8282

client/client.go

+29-1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ type Client struct {
6161

6262
address string
6363
conn net.Conn
64+
lastRx time.Time
6465
started chan struct{}
6566

6667
key *protocol.SecurityKey
@@ -137,6 +138,7 @@ func (c *Client) Connect() error {
137138
go c.reader()
138139
go c.writer()
139140
go c.manage()
141+
go c.pinger()
140142

141143
return nil
142144
}
@@ -215,6 +217,31 @@ func (c *Client) nextRecvMsg(deadline time.Time) (*protocol.PhevMessage, error)
215217
}
216218
}
217219

220+
// Sends periodic pings to the car.
221+
func (c *Client) pinger() {
222+
pingSeq := byte(0xa)
223+
ticker := time.NewTicker(200 * time.Millisecond)
224+
defer ticker.Stop()
225+
for t := range ticker.C {
226+
switch {
227+
case c.closed:
228+
return
229+
case t.Sub(c.lastRx) < 500*time.Millisecond:
230+
continue
231+
}
232+
c.Send <- &protocol.PhevMessage{
233+
Type: protocol.CmdOutPingReq,
234+
Ack: protocol.Request,
235+
Register: pingSeq,
236+
Data: []byte{0x0},
237+
}
238+
pingSeq++
239+
if pingSeq > 0x63 {
240+
pingSeq = 0
241+
}
242+
}
243+
}
244+
218245
// manages the connection, handling control messages.
219246
func (c *Client) manage() {
220247
ml := c.AddListener()
@@ -274,6 +301,7 @@ func (c *Client) reader() {
274301
c.lMu.Unlock()
275302
return
276303
}
304+
c.lastRx = time.Now()
277305
log.Tracef("%%PHEV_TCP_RECV_DATA%%: %s", hex.EncodeToString(data[:n]))
278306
messages := protocol.NewFromBytes(data[:n], c.key)
279307
for _, m := range messages {
@@ -297,9 +325,9 @@ func (c *Client) writer() {
297325
c.conn.Close()
298326
return
299327
}
300-
log.Debugf("%%PHEV_TCP_SEND_MSG%%: [%02x] %s", msg.Xor, msg.ShortForm())
301328
msg.Xor = 0
302329
data := msg.EncodeToBytes(c.key)
330+
log.Debugf("%%PHEV_TCP_SEND_MSG%%: [%02x] %s", msg.Xor, msg.ShortForm())
303331
log.Tracef("%%PHEV_TCP_SEND_DATA%%: %s", hex.EncodeToString(data))
304332
c.conn.(*net.TCPConn).SetWriteDeadline(time.Now().Add(15 * time.Second))
305333
if _, err := c.conn.Write(data); err != nil {

cmd/pcap.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ The decoder filters TCP packets to and from port 8080.
4646
}
4747
defer handle.Close()
4848
securityKey = &protocol.SecurityKey{}
49+
pings, _ = cmd.Flags().GetBool("pings")
4950

5051
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
5152
var currentTime time.Time
@@ -114,6 +115,9 @@ func processPayload(cmd *cobra.Command, data []byte, dir string) {
114115
handleRegisters(msg)
115116
return
116117
}
118+
if !pings && (msg.Type == protocol.CmdOutPingReq || msg.Type == protocol.CmdInPingResp) {
119+
return
120+
}
117121
log.Infof("%s [%02x] %s", dir, msg.Xor, msg.ShortForm())
118122
}
119123
}
@@ -122,11 +126,17 @@ var securityKey *protocol.SecurityKey
122126

123127
var regs = map[byte]string{}
124128

129+
var pings bool
130+
125131
func handleRegisters(m *protocol.PhevMessage) {
126132
if m.Type == protocol.CmdInResp {
127133
data := hex.EncodeToString(m.Data)
128134
if d := regs[m.Register]; d != data {
129-
log.Infof("UPDATEREG 0x%02x: %s -> %s\n", m.Register, d, data)
135+
if m.Reg != nil {
136+
log.Infof("UPDATEREG 0x%02x: %s -> %s (%s)\n", m.Register, d, data, m.Reg.String())
137+
} else {
138+
log.Infof("UPDATEREG 0x%02x: %s -> %s\n", m.Register, d, data)
139+
}
130140
regs[m.Register] = data
131141
}
132142
}
@@ -147,4 +157,5 @@ func init() {
147157
pcapCmd.Flags().StringP("direction", "d", "both", "Direction to decode")
148158
pcapCmd.Flags().BoolP("latency", "l", false, "Replay with original network latency")
149159
pcapCmd.Flags().BoolP("registers", "R", false, "Show register updates")
160+
pcapCmd.Flags().BoolP("pings", "P", false, "Show ping requests and responses")
150161
}

cmd/register.go

+28-6
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,29 @@ import (
3030
var registerCmd = &cobra.Command{
3131
Use: "register",
3232
Short: "Register client with the car",
33-
// Args: cobra.MinimumNArgs(1),
3433
Long: `Register phev2mqtt with the car.
3534
3635
You will need to first put the car into registration mode, then run
3736
this command within 5 minutes.
3837
38+
The car will register the MAC address that connects to it - typically the
39+
MAC address of the TCP client that ultimately connects to the car. If you
40+
are going via a NAT gateway, etc, then bear this in mind.
41+
42+
`,
43+
Run: runRegister,
44+
}
45+
46+
// unRegisterCmd represents the unregister command
47+
var unRegisterCmd = &cobra.Command{
48+
Use: "unregister",
49+
Short: "Un register client from the car",
50+
Long: `Unregister this phev2mqtt instance from the car.
51+
52+
The car will unregister the MAC address that connects to it - typically the
53+
MAC address of the TCP client that ultimately connects to the car. If you
54+
are going via a NAT gateway, etc, then bear this in mind.
55+
3956
`,
4057
Run: runRegister,
4158
}
@@ -94,21 +111,26 @@ func runRegister(cmd *cobra.Command, args []string) {
94111
log.Errorf("Client closed before recieving VIN")
95112
return
96113
}
97-
fmt.Printf("Car VIN is %s - attempting to register...\n", vin)
98114

99115
reg := byte(0x10)
100-
if unreg, _ := cmd.Flags().GetBool("unregister"); unreg {
116+
if cmd.Use == "unregister" {
117+
fmt.Printf("Attempting to unregister from car (VIN: %s)...\n", vin)
101118
reg = 0x15
119+
} else {
120+
fmt.Printf("Attempting to register to car (VIN: %s)...\n", vin)
102121
}
103122
if err := cl.SetRegister(reg, []byte{0x1}); err != nil {
104-
log.Errorf("Failed to register: %v", err)
123+
log.Errorf("Failed to (un)register: %v", err)
105124
return
106125
}
107-
fmt.Printf("Successfully registered!\n")
126+
cl.Close()
127+
time.Sleep(time.Second)
128+
fmt.Printf("Success!\n")
108129
}
109130

110131
func init() {
111132
clientCmd.AddCommand(registerCmd)
133+
clientCmd.AddCommand(unRegisterCmd)
112134

113135
// Here you will define your flags and configuration settings.
114136

@@ -120,5 +142,5 @@ func init() {
120142
// is called directly, e.g.:
121143
// registerCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
122144
registerCmd.Flags().Duration("wait_duration", 10*time.Second, "How long to wait after connecting to car before sending registration command")
123-
registerCmd.Flags().Bool("unregister", false, "Remove existing registration (this mac address)")
145+
unRegisterCmd.Flags().Duration("wait_duration", 10*time.Second, "How long to wait after connecting to car before sending registration command")
124146
}

cmd/root.go

+7
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
var (
2929
cfgFile string
3030
logLevel string
31+
logTimes bool
3132
)
3233

3334
// rootCmd represents the base command when called without any subcommands
@@ -42,6 +43,11 @@ var rootCmd = &cobra.Command{
4243
panic(err)
4344
}
4445
log.SetLevel(level)
46+
if logTimes {
47+
log.SetFormatter(&log.TextFormatter{
48+
FullTimestamp: true,
49+
})
50+
}
4551
},
4652

4753
// Uncomment the following line if your bare application
@@ -64,6 +70,7 @@ func init() {
6470

6571
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.phev2mqtt.yaml)")
6672
rootCmd.PersistentFlags().StringVarP(&logLevel, "verbosity", "v", "info", "logging level to use")
73+
rootCmd.PersistentFlags().BoolVarP(&logTimes, "log_timestamps", "t", false, "logging with timestamps")
6774

6875
// Cobra also supports local flags, which will only run
6976
// when this action is called directly.

cmd/set.go

+18
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ package cmd
1818

1919
import (
2020
"encoding/hex"
21+
"fmt"
2122
"strings"
23+
"time"
2224

2325
"github.com/buxtronix/phev2mqtt/client"
2426
// log "github.com/sirupsen/logrus"
@@ -70,6 +72,15 @@ func runSet(cmd *cobra.Command, args []string) {
7072
}
7173
}
7274

75+
waitTime, err := cmd.Flags().GetDuration("wait_duration")
76+
if err != nil {
77+
panic(err)
78+
}
79+
sendInterval, err := cmd.Flags().GetDuration("send_interval")
80+
if err != nil {
81+
panic(err)
82+
}
83+
7384
address, _ := cmd.Flags().GetString("address")
7485
cl, err := client.New(client.AddressOption(address))
7586
if err != nil {
@@ -83,11 +94,16 @@ func runSet(cmd *cobra.Command, args []string) {
8394
if err := cl.Start(); err != nil {
8495
panic(err)
8596
}
97+
fmt.Printf("Client connected and started!\nWaiting %d\n", waitTime.String())
98+
99+
time.Sleep(waitTime)
86100

87101
for _, reg := range setRegisters {
102+
fmt.Printf("Setting register 0x%x to 0x%s\n", reg.register, hex.EncodeToString(reg.value))
88103
if err := cl.SetRegister(reg.register, reg.value); err != nil {
89104
panic(err)
90105
}
106+
time.Sleep(sendInterval)
91107
}
92108

93109
}
@@ -104,4 +120,6 @@ func init() {
104120
// Cobra supports local flags which will only run when this command
105121
// is called directly, e.g.:
106122
// registerCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
123+
setCmd.Flags().Duration("wait_duration", 10*time.Second, "How long to wait after connecting to car before sending register update")
124+
setCmd.Flags().Duration("send_interval", 1*time.Second, "Interval between setting each register")
107125
}

go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ go 1.16
55
require (
66
github.com/d4l3k/messagediff v1.2.1 // indirect
77
github.com/eclipse/paho.mqtt.golang v1.3.5
8+
github.com/google/btree v1.0.0 // indirect
89
github.com/google/gopacket v1.1.19
910
github.com/sirupsen/logrus v1.8.1
1011
github.com/spf13/cobra v1.2.1
1112
github.com/spf13/viper v1.8.1
13+
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 // indirect
1214
gopkg.in/d4l3k/messagediff.v1 v1.2.1
1315
)

protocol/message.go

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package protocol
33
import (
44
"encoding/hex"
55
"fmt"
6+
log "github.com/sirupsen/logrus"
67
)
78

89
const (
@@ -134,6 +135,7 @@ func (p *PhevMessage) EncodeToBytes(key *SecurityKey) []byte {
134135
// Use but do not increment send key.
135136
xor = key.SKey(false)
136137
}
138+
p.Xor = xor
137139
return XorMessageWith(data, xor)
138140
}
139141

@@ -200,6 +202,7 @@ func (p *PhevMessage) String() string {
200202
func NewFromBytes(data []byte, key *SecurityKey) []*PhevMessage {
201203
msgs := []*PhevMessage{}
202204

205+
log.Tracef("%%PHEV_DECODE_FROM_BYTES%%: Raw: %s", hex.EncodeToString(data))
203206
offset := 0
204207
for {
205208
dat, xor, rem := ValidateAndDecodeMessage(data[offset:])
@@ -210,6 +213,7 @@ func NewFromBytes(data []byte, key *SecurityKey) []*PhevMessage {
210213
}
211214
continue
212215
}
216+
log.Tracef("%%PHEV_DECODED_FROM_BYTES%%: Raw: %s", hex.EncodeToString(dat))
213217
dat = XorMessageWith(dat, xor)
214218
p := &PhevMessage{}
215219
err := p.DecodeFromBytes(dat, key)
@@ -224,6 +228,7 @@ func NewFromBytes(data []byte, key *SecurityKey) []*PhevMessage {
224228
break
225229
}
226230
data = rem
231+
offset = 0
227232
}
228233
return msgs
229234
}

0 commit comments

Comments
 (0)