├── .gitignore
├── README
├── clients
├── README
├── cfgconv.c
├── client_app.pl
├── cmd.pl
├── diagmode
│ ├── dcf_download.pl
│ └── diagcmd.pl
├── edimax
│ ├── edimax_check.sh
│ ├── edimax_config.txt
│ ├── edimax_crontab.txt
│ ├── edimax_get.sh
│ ├── edimax_readme.txt
│ └── edimax_switch.sh
├── homematic
│ ├── Homematic.sh
│ ├── README_HOMEMATIC
│ ├── check.sh
│ ├── config.txt
│ ├── homematic_crontab.txt
│ └── location.sh
├── notify.pl
├── ovms_client.conf
├── querybalance.sh
├── rt_fetchdata.sh
├── rt_fetchlogs.sh
├── rt_querylogs.sh
├── rt_resetlogs.sh
├── serverlog.sh
├── status.pl
└── subscribe.pl
├── docs
├── OVMS_Protocol.docx
└── OVMS_Protocol.pdf
├── drupal
├── images
│ ├── delete.gif
│ ├── edit.gif
│ ├── key.gif
│ └── provision.png
├── openvehicles.info
└── openvehicles.module
└── v3
├── README
├── demos
├── ovms_democar.pl
├── ovms_rally.data
└── ovms_rally.pl
├── server
├── SETUP
├── conf
│ └── ovms_server.conf.default
├── ovms_server.pl
├── ovms_server.sql
├── ovms_server_v2_to_v3.sql
├── plugins
│ ├── obsolete
│ │ └── OVMS
│ │ │ └── Server
│ │ │ ├── ApiHttpGroup.pm
│ │ │ ├── AuthConfig.pm
│ │ │ └── Cluster.pm
│ └── system
│ │ └── OVMS
│ │ └── Server
│ │ ├── ApiHttp.pm
│ │ ├── ApiHttpCore.pm
│ │ ├── ApiHttpFile.pm
│ │ ├── ApiHttpMqapi.pm
│ │ ├── ApiV2.pm
│ │ ├── AuthDrupal.pm
│ │ ├── AuthNone.pm
│ │ ├── Core.pm
│ │ ├── DbDBI.pm
│ │ ├── Plugin.pm
│ │ ├── Push.pm
│ │ ├── PushAPNS.pm
│ │ ├── PushGCM.pm
│ │ ├── PushMAIL.pm
│ │ └── VECE.pm
└── vece
│ ├── o2.vece
│ ├── tr.vece
│ └── va.vece
├── shell
├── ovms_shell.conf.default
└── ovms_shell.pl
├── support
├── ovms.logrotate
├── ovms_democar.service
└── ovms_server.service
└── utils
├── ap_check.pl
├── ap_generate.pl
└── client_car.pl
/.gitignore:
--------------------------------------------------------------------------------
1 | # Server configuration
2 | v3/server/conf/ovms_server.conf
3 | v3/server/conf/*.pem
4 |
5 | # Shell configuration
6 | v3/shell/ovms_shell.conf
7 |
8 | # Local plugins
9 | v3/server/plugins/local/
10 | v3/server/httpfiles/
11 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | Open Vehicle Monitoring System - Server
2 | =======================================
3 |
4 | This repository contains server code, and support, for the the Open Vehicle Monitoring System.
5 |
6 | The v2 Server
7 | =============
8 |
9 | The v2 server is a simple open source server which talks two protocols - one to the car and the other to
10 | the cellphone App.
11 |
12 | The car protocol is built on TCP using encrypted communication packets. See the documentation
13 | directory on github for further details on the protocol itself.
14 |
15 | The drupal server also includes a web interface for basic functions such as setting the password,
16 | registering the car and checking status (see 'drupal' directory in github).
17 |
18 | To deploy an OVMS server, you will need the ovms_server.pl script, the ovms_server.conf configuration
19 | file suitably configured, the ovms_server.sql database tables in a mysql database, and optionally
20 | the *.vece files (to convert error code to messages for different vehicle types).
21 |
22 | The other scripts are purely for development and testing purposes.
23 |
24 |
--------------------------------------------------------------------------------
/clients/README:
--------------------------------------------------------------------------------
1 | Open Vehicle Monitoring System
2 | ==============================
3 |
4 |
5 | Perl modules
6 | ============
7 |
8 | The following perl modules from CPAN are used by the clients:
9 |
10 | Config::IniFiles
11 | Digest::MD5
12 | Digest::HMAC
13 | Crypt::RC4::XS
14 | MIME::Base64
15 | IO::Socket::INET
16 | IO::Socket::Timeout
17 |
18 | ...additionally for the diag mode clients (serial connection):
19 | Device::SerialPort (UNIX)
20 | Win32::SerialPort (Windows)
21 |
22 | Install modules using cpanminus:
23 | cpanm Config::IniFiles Digest::MD5 Digest::HMAC Crypt::RC4::XS MIME::Base64 \
24 | IO::Socket::INET IO::Socket::Timeout Device::SerialPort
25 |
26 | A note on Crypt::RC4::XS
27 | ========================
28 |
29 | There are two version of Crypt::RC4 in perl. The first is the pure perl implementation Crypt::RC4,
30 | and the second is the C code wrapped in an XS implementation Crypt::RC4::XS.
31 |
32 | A rough comparison:
33 | 10,000 iterations of RC4 encryption on a block of 26KB plain text.
34 | Crypt::RC4 399.20 seconds
35 | Crypt::RC4::XS 2.54 seconds
36 |
37 | In general, we recommend using Crypt::RC4::XS as it is significantly faster (at least 100x faster).
38 |
39 | However, if you want to use Crypt::RC4 (the slower pure perl implementation), perhaps because
40 | it is in your distribution and doesn't need to be installed via cpan, then you can. But change
41 | all references to Crypt::RC4::XS to Crypt::RC4 in the perl client code.
42 |
43 | The clients
44 | ===========
45 |
46 | The scripts "client_app.pl", "status.pl" and "cmd.pl" can be used to access the OVMS server from the
47 | command line.
48 |
49 | All will read "ovms_client.conf" for the server and login configuration. Change to your vehicle id,
50 | server and module password.
51 |
52 | "client_app.pl" will connect to the server, listen and output any protocol lines received forever
53 | until stopped (Ctrl-C).
54 |
55 | "status.pl" retrieves the last server stored record of a standard OVMS status protocol message.
56 | You may specify the message type (letter) as an argument:
57 |
58 | S State (SOC, charge state etc.) (Default)
59 | D Environment (temperatures etc.)
60 | L Location
61 | W Tire pressures
62 | T Update time
63 | F Firmware version
64 | V Firmware capabilities
65 |
66 | See "OVMS protocol" documentation for record details.
67 |
68 | "cmd.pl" can be used to send single commands to the server/car. It will output only server responses
69 | that match the command issued and terminate after having received no more command results for a
70 | defined time.
71 |
72 | "cmd.pl" can be used to query historical record entries from the server or to automate car communication.
73 |
74 | Examples:
75 |
76 | Query stored historical records:
77 | #> ./cmd.pl 31
78 |
79 | Retrieve all records of type "*-OVM-DebugCrash":
80 | #> ./cmd.pl 32 "*-OVM-DebugCrash"
81 |
82 | Send USSD code to query SIM account balance:
83 | #> ./cmd.pl 41 "*100#"
84 | There's also the "querybalance.sh" script to do this.
85 |
86 |
87 | Push notifications & subscriptions
88 | ==================================
89 |
90 | Use "notify.pl" to send a push notification alert text message to your Apps:
91 |
92 | #> ./notify.pl 'Hello World!'
93 |
94 | If the server has mail notification support enabled you can use "subscribe.pl" to
95 | manage your email subscriptions.
96 |
97 | Subscribe:
98 | #> ./subscribe.pl joe joe@email.com
99 | Unsubscribe:
100 | #> ./subscribe.pl joe -
101 |
102 |
103 | Renault Twizy scripts
104 | =====================
105 |
106 | "rt_fetchdata.sh" will fetch all Twizy specific track telemetry records and save them using a
107 | common filename prefix.
108 |
109 | "rt_querylogs.sh" will query all SEVCON logs, i.e. ask the module to query them and send
110 | the history records to the server.
111 |
112 | "rt_fetchlogs.sh" can then be used to fetch all logs records from the server into local
113 | files (similar to rt_fetchdata).
114 |
115 | "rt_resetlogs.sh" will reset all SEVCON logs. Please note: not all log entries
116 | may be clearable, query and fetch again to check the log contents after reset.
117 |
118 |
119 | The diag mode clients
120 | =====================
121 |
122 | The diag mode clients can be used additionally or instead of a terminal emulator
123 | to access the module via serial line.
124 |
125 | Both clients offer command line help using the "--help" option.
126 |
127 | "diagcmd.pl" sends an arbitrary number of commands to the module and outputs
128 | their responses. (Note: if you want to redirect the output you need to
129 | explicitly do "perl diagcmd.pl ..." instead of "./diagcmd.pl ...")
130 |
131 | Example: init diag mode and read out car status:
132 |
133 | #> ./diagcmd.pl --init "s stat"
134 |
135 | Note: you only need to do "--init" once (this will listen for the module
136 | startup and issue the "SETUP" command).
137 |
138 | "dcf_download.pl" uses the Twizy CFG commands to download the register dictionary
139 | and current values from the SEVCON controller and write a DCF in CSV format.
140 |
141 | Example:
142 |
143 | #> ./dcf_download.pl --init --output=my-config.csv
144 |
145 | Note: as every single SDO read needs to be issued through the CFG command interface
146 | this is a long operation, expect ~ 1 hour for a complete download. Progress is reported,
147 | and you can resume a broken download using the "--start" option.
148 |
149 |
150 | Utilities: cfgconv
151 | ==================
152 |
153 | "cfgconv" converts SEVCON profiles from text (commands/tags) to base64 and vice versa.
154 | See Twizy commands "CFG GET" and "CFG SET" for details.
155 |
156 | Build:
157 | gcc -o cfgconv cfgconv.c
158 |
159 | Use:
160 | a) convert base64 to commands: ./cfgconv base64data > profile.txt
161 | b) convert commands to base64: ./cfgconv < profile.txt
162 | c) convert base64 to tags: ./cfgconv -t base64data > profile.inf
163 | d) convert tags to base64: ./cfgconv -t < profile.inf
164 |
165 |
166 |
--------------------------------------------------------------------------------
/clients/client_app.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 |
3 | use Digest::MD5;
4 | use Digest::HMAC;
5 | use Crypt::RC4::XS;
6 | use MIME::Base64;
7 | use IO::Socket::INET;
8 | use Config::IniFiles;
9 |
10 | my $b64tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
11 |
12 | # Configuration
13 | my $config = Config::IniFiles->new(-file => 'ovms_client.conf');
14 |
15 | my $vehicle_id = $config->val('client','vehicle_id','TESTCAR');
16 | my $server_password = $config->val('client','server_password','NETPASS');
17 | my $module_password = $config->val('client','module_password','OVMS');
18 |
19 | my $server_ip = $config->val('client','server_ip','54.197.255.127');
20 |
21 | print STDERR "Shared Secret is: ",$server_password,"\n";
22 |
23 | #####
24 | ##### CONNECT
25 | #####
26 |
27 | my $sock = IO::Socket::INET->new(PeerAddr => $server_ip,
28 | PeerPort => '6867',
29 | Proto => 'tcp');
30 |
31 | #####
32 | ##### CLIENT
33 | #####
34 |
35 | print STDERR "I am the client\n";
36 |
37 | rand;
38 | my $client_token;
39 | foreach (0 .. 21)
40 | { $client_token .= substr($b64tab,rand(64),1); }
41 | print STDERR " Client token is $client_token\n";
42 |
43 | my $client_hmac = Digest::HMAC->new($server_password, "Digest::MD5");
44 | $client_hmac->add($client_token);
45 | my $client_digest = $client_hmac->b64digest();
46 | print STDERR " Client digest is $client_digest\n";
47 |
48 | # Register as App client (type "A"):
49 | print $sock "MP-A 0 $client_token $client_digest $vehicle_id\r\n";
50 |
51 | my $line = <$sock>;
52 | chop $line;
53 | chop $line;
54 | print STDERR " Received $line from server\n";
55 | my ($welcome,$crypt,$server_token,$server_digest) = split /\s+/,$line;
56 |
57 | print STDERR " Received $server_token $server_digest from server\n";
58 |
59 | my $d_server_digest = decode_base64($server_digest);
60 | my $client_hmac = Digest::HMAC->new($server_password, "Digest::MD5");
61 | $client_hmac->add($server_token);
62 | if ($client_hmac->digest() ne $d_server_digest)
63 | {
64 | print STDERR " Client detects server digest is invalid - aborting\n";
65 | exit(1);
66 | }
67 | print STDERR " Client verification of server digest is ok\n";
68 |
69 | $client_hmac = Digest::HMAC->new($server_password, "Digest::MD5");
70 | $client_hmac->add($server_token);
71 | $client_hmac->add($client_token);
72 | my $client_key = $client_hmac->digest;
73 | print STDERR " Client version of shared key is: ",encode_base64($client_key,''),"\n";
74 |
75 | my $txcipher = Crypt::RC4::XS->new($client_key);
76 | $txcipher->RC4(chr(0) x 1024); # Prime the cipher
77 | my $rxcipher = Crypt::RC4::XS->new($client_key);
78 | $rxcipher->RC4(chr(0) x 1024); # Prime the cipher
79 |
80 | my $encrypted = encode_base64($txcipher->RC4("MP-0 A"),'');
81 | print STDERR " Sending message $encrypted\n";
82 | print $sock "$encrypted\r\n";
83 |
84 | my $ptoken = "";
85 | my $pdigest = "";
86 | while(<$sock>)
87 | {
88 | chop; chop;
89 | print STDERR " Received $_ from server\n";
90 | my $decoded = $rxcipher->RC4(decode_base64($_));
91 | print STDERR " Server message decodes to: ",$decoded,"\n";
92 |
93 | if ($decoded =~ /^MP-0 ET(.+)/)
94 | {
95 | $ptoken = $1;
96 | print STDERR " Paranoid token $ptoken received\n";
97 | my $paranoid_hmac = Digest::HMAC->new($module_password, "Digest::MD5");
98 | $paranoid_hmac->add($ptoken);
99 | $pdigest = $paranoid_hmac->digest;
100 | }
101 | elsif ($decoded =~ /^MP-0 EM(.)(.*)/)
102 | {
103 | my ($code,$data) = ($1,$2);
104 | my $pmcipher = Crypt::RC4::XS->new($pdigest);
105 | $pmcipher->RC4(chr(0) x 1024); # Prime the cipher
106 | $decoded = $pmcipher->RC4(decode_base64($data));
107 | print STDERR " Paranoid message $code $decoded\n";
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/clients/cmd.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 |
3 | use Digest::MD5;
4 | use Digest::HMAC;
5 | use Crypt::RC4::XS;
6 | use MIME::Base64;
7 | use IO::Socket::INET;
8 | use IO::Socket::Timeout;
9 | use Errno qw(ETIMEDOUT EWOULDBLOCK);
10 | use Config::IniFiles;
11 |
12 | # Read command line arguments:
13 | my $cmd_code = $ARGV[0];
14 | my $cmd_args = $ARGV[1];
15 | my $cmd_rescnt = $ARGV[2];
16 |
17 | if (!$cmd_code) {
18 | print "usage: cmd.pl code [args] [resultcnt]\n";
19 | exit;
20 | }
21 |
22 | my $b64tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
23 |
24 | # Configuration
25 | my $config = Config::IniFiles->new(-file => 'ovms_client.conf');
26 |
27 | my $vehicle_id = $config->val('client','vehicle_id','TESTCAR');
28 | my $server_password = $config->val('client','server_password','NETPASS');
29 | my $module_password = $config->val('client','module_password','OVMS');
30 |
31 | my $server_ip = $config->val('client','server_ip','tmc.openvehicles.com');
32 |
33 |
34 | #####
35 | ##### CONNECT
36 | #####
37 |
38 | my $sock = IO::Socket::INET->new(
39 | PeerAddr => $server_ip,
40 | PeerPort => '6867',
41 | Proto => 'tcp',
42 | Timeout => 5 );
43 |
44 | # configure socket timeouts:
45 | IO::Socket::Timeout->enable_timeouts_on($sock);
46 | $sock->read_timeout(20);
47 | $sock->write_timeout(20);
48 |
49 |
50 | #####
51 | ##### REGISTER
52 | #####
53 |
54 | rand;
55 | my $client_token;
56 | foreach (0 .. 21)
57 | { $client_token .= substr($b64tab,rand(64),1); }
58 |
59 | my $client_hmac = Digest::HMAC->new($server_password, "Digest::MD5");
60 | $client_hmac->add($client_token);
61 | my $client_digest = $client_hmac->b64digest();
62 |
63 | # Register as batch client (type "B"):
64 | print $sock "MP-B 0 $client_token $client_digest $vehicle_id\r\n";
65 |
66 | my $line = <$sock>;
67 | chop $line;
68 | chop $line;
69 | my ($welcome,$crypt,$server_token,$server_digest) = split /\s+/,$line;
70 |
71 | my $d_server_digest = decode_base64($server_digest);
72 | my $client_hmac = Digest::HMAC->new($server_password, "Digest::MD5");
73 | $client_hmac->add($server_token);
74 | if ($client_hmac->digest() ne $d_server_digest)
75 | {
76 | print STDERR " Client detects server digest is invalid - aborting\n";
77 | exit(1);
78 | }
79 |
80 | $client_hmac = Digest::HMAC->new($server_password, "Digest::MD5");
81 | $client_hmac->add($server_token);
82 | $client_hmac->add($client_token);
83 | my $client_key = $client_hmac->digest;
84 |
85 | my $txcipher = Crypt::RC4::XS->new($client_key);
86 | $txcipher->RC4(chr(0) x 1024); # Prime the cipher
87 | my $rxcipher = Crypt::RC4::XS->new($client_key);
88 | $rxcipher->RC4(chr(0) x 1024); # Prime the cipher
89 |
90 |
91 | #####
92 | ##### SEND COMMAND
93 | #####
94 |
95 | my $cmd;
96 | if ($cmd_args)
97 | { $cmd = $cmd_code.",".$cmd_args; }
98 | else
99 | { $cmd = $cmd_code; }
100 |
101 | my $encrypted = encode_base64($txcipher->RC4("MP-0 C".$cmd),'');
102 | print $sock "$encrypted\r\n";
103 |
104 |
105 | #####
106 | ##### READ RESPONSE
107 | #####
108 |
109 | my $ptoken = "";
110 | my $pdigest = "";
111 | my $data = "";
112 | my $discardcnt = 0;
113 | my $resultcnt = 0;
114 |
115 | while(1)
116 | {
117 | # Read from server:
118 | $data = <$sock>;
119 | if (! $data && ( 0+$! == ETIMEDOUT || 0+$! == EWOULDBLOCK )) {
120 | print STDERR "Read timeout, exit.\n";
121 | exit;
122 | }
123 |
124 | chop $data; chop $data;
125 | my $decoded = $rxcipher->RC4(decode_base64($data));
126 |
127 | if ($decoded =~ /^MP-0 ET(.+)/)
128 | {
129 | $ptoken = $1;
130 | my $paranoid_hmac = Digest::HMAC->new($module_password, "Digest::MD5");
131 | $paranoid_hmac->add($ptoken);
132 | $pdigest = $paranoid_hmac->digest;
133 | # discard:
134 | $decoded = "";
135 | }
136 | elsif ($decoded =~ /^MP-0 EM(.)(.*)/)
137 | {
138 | my ($code,$data) = ($1,$2);
139 | my $pmcipher = Crypt::RC4::XS->new($pdigest);
140 | $pmcipher->RC4(chr(0) x 1024); # Prime the cipher
141 | $decoded = $pmcipher->RC4(decode_base64($data));
142 | # reformat as std msg:
143 | $decoded = "MP-0 ".$code.$decoded;
144 | }
145 |
146 | if ($decoded =~ /^MP-0 c$cmd_code/)
147 | {
148 | print STDOUT $decoded,"\n";
149 | $discardcnt = 0;
150 | $resultcnt++;
151 | if (($cmd_rescnt ne 0) && ($resultcnt >= $cmd_rescnt))
152 | {
153 | exit;
154 | }
155 | }
156 | else
157 | {
158 | # exit if more than 25 other msgs received in series (assuming cmd done):
159 | $discardcnt++;
160 | if ($discardcnt > 25)
161 | {
162 | print STDERR "No more results, exit.\n";
163 | exit;
164 | }
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/clients/diagmode/dcf_download.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 | #
3 | # Purpose: Read Twizy SEVCON DCF (register dump), output in CSV format
4 | # Needs: OVMS Twizy firmware with DIAG mode support
5 | # Author: Michael Balzer
6 | # Version: 1.0 (2015/12/25)
7 | # Requires: Win32::SerialPort for Windows or Device::SerialPort for UNIX
8 | #
9 | # Permission is hereby granted, free of charge, to any person obtaining a copy
10 | # of this software and associated documentation files (the "Software"), to deal
11 | # in the Software without restriction, including without limitation the rights
12 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | # copies of the Software, and to permit persons to whom the Software is
14 | # furnished to do so, subject to the following conditions:
15 | #
16 | # The above copyright notice and this permission notice shall be included in
17 | # all copies or substantial portions of the Software.
18 | #
19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | # THE SOFTWARE.
26 | #
27 |
28 | use Getopt::Long;
29 | use Symbol qw( gensym );
30 | use IO::Handle;
31 |
32 |
33 | #
34 | # Command line arguments:
35 | #
36 |
37 | my $help = '';
38 | my $debug = '';
39 | my $init = '';
40 | my $device = '/dev/ttyUSB0';
41 | my $outfile = 'twizy-dcf.csv';
42 | my $start = 1;
43 |
44 | GetOptions (
45 | 'help' => \$help,
46 | 'debug' => \$debug,
47 | 'init' => \$init,
48 | 'device=s' => \$device,
49 | 'outfile=s' => \$outfile,
50 | 'start=i' => \$start );
51 |
52 | if ($help) {
53 | print STDOUT
54 | "Usage: $0 [options]\n",
55 | "\n",
56 | "Read dictionary/DCF from SEVCON and output to CSV file.\n",
57 | "Note: the dictionary contains >1200 registers, full download will need ~ 1 hour.\n",
58 | "\n",
59 | "Options:\n",
60 | " --help show this help\n",
61 | " --debug enable debug output\n",
62 | " --init initialize OVMS DIAG mode (use on module reset/powerup)\n",
63 | " --device=... specify serial device, default=/dev/ttyUSB0\n",
64 | " --outfile=... specify output file name, default=twizy-dcf.csv\n",
65 | " --start=n start reading dictionary at entry #n (default 1)\n",
66 | "\n",
67 | "Use --start to resume a broken download, n>1 = append to CSV file.\n",
68 | "\n";
69 | exit;
70 | }
71 |
72 |
73 | #
74 | # Global variables:
75 | #
76 |
77 | my $line;
78 | my @response;
79 |
80 |
81 | #
82 | # Open serial port
83 | #
84 |
85 | my $handle = gensym();
86 | my $port;
87 |
88 | if ( $^O =~ m/Win32/ ) {
89 | require Win32::SerialPort;
90 | $port = tie (*$handle, "Win32::SerialPort", $device)
91 | or die "Cannot open $device\n";
92 | }
93 | else {
94 | require Device::SerialPort;
95 | $port = tie (*$handle, "Device::SerialPort", $device)
96 | or die "Cannot open $device\n";
97 | }
98 |
99 | $port->user_msg(OFF);
100 | $port->baudrate(9600);
101 | $port->parity("none");
102 | $port->databits(8);
103 | $port->stopbits(1);
104 | $port->handshake("none");
105 |
106 | $port->read_char_time(0);
107 | $port->read_const_time(1000);
108 | $port->are_match("\r","\n");
109 |
110 | $port->write_settings;
111 |
112 | $port->purge_all;
113 |
114 |
115 | #
116 | # wait_for: wait for specific input line with timeout
117 | # removes CR/LF and modem responses (OK/ERROR) from input
118 | # returns undef if timeout occurs
119 | #
120 | sub wait_for {
121 | my $pattern = shift // '.'; # valid input pattern
122 | my $timeout = shift // 10; # read timeout in seconds / 0=no timeout
123 |
124 | my $i = 0;
125 | do {
126 | my $line = <$handle>;
127 |
128 | $i++ if !$line;
129 |
130 | if ($debug) {
131 | print STDOUT ($line ne "") ? "$line\n" : "... [wait $i]\n";
132 | }
133 |
134 | # remove CR/LF and modem responses
135 | $line =~ s/[\r\n]//g;
136 | $line =~ s/^(OK|ERROR)$//g;
137 |
138 | return $line if ($line =~ /$pattern/);
139 |
140 | } while ($timeout == 0 or $i < $timeout);
141 |
142 | return undef;
143 | }
144 |
145 |
146 | #
147 | # do_cmd: send OVMS command, retry on timeout, return response (array of lines)
148 | #
149 | # Example: send a command, read response lines until end tag or timeout:
150 | # @response = do_cmd('S STAT');
151 | #
152 | sub do_cmd {
153 | my $cmd = shift // ''; # empty = don't send, just listen
154 | my $expect = shift // '^# '; # response intro pattern (will be removed from result)
155 | my $timeout = shift // 10; # read timeout in seconds
156 | my $tries = shift // 3; # number of send attempts
157 | my $endtag = shift // '^#.$'; # response end pattern (will be removed from result)
158 |
159 | my @result;
160 | my $line;
161 | my $try;
162 |
163 | print STDOUT "*** COMMAND: $cmd\n" if $debug;
164 |
165 | for ($try = 1; $try <= $tries; $try++) {
166 |
167 | # send command:
168 | if ($cmd ne "") {
169 | print STDERR "*** COMMAND RETRY: $cmd\n" if $try > 1;
170 | print $handle "$cmd\r";
171 | }
172 |
173 | return undef if $expect eq "";
174 |
175 | # wait for response intro:
176 | $line = wait_for($expect, $timeout);
177 |
178 | # retry send on timeout:
179 | next if !defined($line);
180 |
181 | # remove intro pattern:
182 | $line =~ s/$expect//;
183 |
184 | # save response:
185 | push @result, $line;
186 |
187 | # read more lines:
188 | while (($linecnt == 0 || --$linecnt > 0) && defined($line = wait_for('.', 1))) {
189 | print STDOUT "+ $line\n" if $debug;
190 | $line =~ s/$expect//;
191 | last if $line =~ /$endtag/;
192 | push @result, $line;
193 | }
194 |
195 | # done:
196 | return @result;
197 | }
198 |
199 | # no success:
200 | print STDERR "*** COMMAND FAILED: no response for '$cmd'\n";
201 | return undef;
202 | }
203 |
204 |
205 |
206 | #
207 | # do_init: enter OVMS diagnostic mode
208 | #
209 | sub do_init {
210 |
211 | GODIAG: while (1) {
212 | print STDOUT "*** INIT: PLEASE POWER UP OR RESET OVMS MODULE NOW\n";
213 | $line = wait_for('RDY') || next GODIAG;
214 | @response = do_cmd('SETUP', 'OVMS DIAGNOSTICS MODE', 30) && last GODIAG;
215 | }
216 |
217 | print STDOUT "*** INIT: DIAG MODE ESTABLISHED\n";
218 | }
219 |
220 |
221 | #
222 | # check_response: output error, return 1 on success or 0 on error/timeout
223 | #
224 | sub check_response {
225 | if (@response == 0 || @response[0] eq "") {
226 | # no response / timeout
227 | return 0;
228 | }
229 | elsif (@response[0] =~ /ERROR/i) {
230 | print STDERR "!!! @response[0]\n";
231 | return 0;
232 | }
233 | else {
234 | return 1;
235 | }
236 | }
237 |
238 |
239 | #
240 | # do_cfg: general purpose CFG command wrapper
241 | #
242 | sub do_cfg {
243 | my $cmd = shift;
244 | @response = do_cmd("S CFG $cmd");
245 | return check_response();
246 | }
247 |
248 |
249 | #
250 | # sdo_read: CFG READ wrapper, returns value or undef on timeout
251 | #
252 | sub sdo_read {
253 | my $index = shift;
254 | my $subindex = shift;
255 |
256 | # read SDO:
257 | @response = do_cmd(sprintf("S CFG READ %04x %02x", $index, $subindex));
258 | return undef if !check_response();
259 |
260 | # extract value:
261 | my $val = (@response[0] =~ / = ([0-9]+)/) ? $1 : undef;
262 | return $val;
263 | }
264 |
265 |
266 | #
267 | # sdo_readstr: CFG READS wrapper, returns string or undef on timeout
268 | #
269 | sub sdo_readstr {
270 | my $index = shift;
271 | my $subindex = shift;
272 |
273 | # read SDO:
274 | @response = do_cmd(sprintf("S CFG READS %04x %02x", $index, $subindex));
275 | return undef if !check_response();
276 |
277 | # extract value:
278 | my $val = (@response[0] =~ /[^=]+=(.*)/) ? $1 : undef;
279 | return $val;
280 | }
281 |
282 |
283 | #
284 | # sdo_write: CFG WRITE wrapper, returns 1/0 for success/fail, undef for timeout
285 | #
286 | sub sdo_write {
287 | my $index = shift;
288 | my $subindex = shift;
289 | my $val = shift;
290 |
291 | # write SDO:
292 | @response = do_cmd(sprintf("S CFG WRITE %04x %02x %ld", $index, $subindex, $val));
293 | return undef if !check_response();
294 |
295 | # extract new value:
296 | my $newval = (@response[0] =~ / NEW: ([0-9]+)/) ? $1 : undef;
297 | return ($newval == $val) ? 1 : 0;
298 | }
299 |
300 |
301 | #
302 | # sdo_writeonly: CFG WRITEO wrapper, returns 1=success or undef=timeout
303 | #
304 | sub sdo_writeonly {
305 | my $index = shift;
306 | my $subindex = shift;
307 | my $val = shift;
308 |
309 | # write SDO:
310 | @response = do_cmd(sprintf("S CFG WRITEO %04x %02x %ld", $index, $subindex, $val));
311 | return undef if !check_response();
312 |
313 | return 1;
314 | }
315 |
316 |
317 |
318 | # ====================================================================
319 | # MAIN
320 | # ====================================================================
321 |
322 | my $index;
323 | my $subindex;
324 | my $datatype;
325 | my $access;
326 | my $lowlimit;
327 | my $highlimit;
328 | my $objversion;
329 | my $val;
330 | my $str;
331 |
332 | my @datatypename = (
333 | 'Boolean', 'Integer8', 'Integer16', 'Integer32', 'Integer64',
334 | 'Unsigned8', 'Unsigned16', 'Unsigned32', 'Unsigned64', 'VisibleString',
335 | 'Domain', 'OctetString', 'Real32', 'Real64', 'Void',
336 | 'Domain' );
337 | my @accessname = (
338 | 'RO', 'WO', 'RW', 'CONST' );
339 |
340 |
341 | # open CSV output file:
342 | my $csv;
343 | if ($start == 1) {
344 | # start new CSV:
345 | open($csv, ">", $outfile) or die "Cannot open output file '$outfile'\n";
346 | print $csv "EntryNr,Index,SubIndex,Version,DataType,Access,LowLimit,HighLimit,ValueHex,ValueDec,ValueStr\n";
347 | }
348 | else {
349 | # append to CSV:
350 | open($csv, ">>", $outfile) or die "Cannot open output file '$outfile'\n";
351 | }
352 | $csv->autoflush(1);
353 |
354 |
355 | # enter DIAG & PREOP mode:
356 | do_init if $init;
357 | print STDOUT "Entering PREOP mode...\n";
358 | do_cfg("PRE") or die "Cannot enter PREOP mode\n";
359 |
360 |
361 | # read dictionary length:
362 | my $diclen = sdo_read(0x5630, 0x01) or die "Cannot read dictionary length\n";
363 | print STDOUT "Dictionary length: $diclen entries\n";
364 |
365 |
366 | # read dictionary entries:
367 | for (my $i = $start; $i <= $diclen; $i++) {
368 |
369 | # read metadata:
370 | sdo_writeonly(0x5630, 0x01, $i-1) or die "Cannot select dictionary entry $i\n";
371 | $index = sdo_read(0x5630, 0x02);
372 | $subindex = sdo_read(0x5630, 0x03);
373 | $datatype = sdo_read(0x5630, 0x05);
374 | $access = sdo_read(0x5630, 0x06);
375 | $lowlimit = sdo_read(0x5630, 0x07);
376 | $highlimit = sdo_read(0x5630, 0x08);
377 | $objversion = sdo_read(0x5630, 0x0a);
378 |
379 | # read value:
380 | if ($access == 1) {
381 | # write-only:
382 | $val = undef;
383 | $str = undef;
384 | }
385 | elsif ($datatype >= 0 && $datatype <= 8) {
386 | # numerical:
387 | $val = sdo_read($index, $subindex);
388 | $str = undef;
389 | }
390 | elsif ($datatype == 9) {
391 | # visible_string:
392 | $val = undef;
393 | $str = sdo_readstr($index, $subindex);
394 | # CSV encode:
395 | $str =~ s/\"/\"\"/g;
396 | $str = '"' . $str . '"';
397 | }
398 | else {
399 | # unsupported datatype:
400 | $val = undef;
401 | $str = undef;
402 | }
403 |
404 | # write CSV record:
405 | if (defined($val)) {
406 | printf $csv "%d,0x%04x,0x%02x,%d,%s,%s,%ld,%ld,0x%lx,%ld,\n",
407 | $i, $index, $subindex, $objversion, @datatypename[$datatype], @accessname[$access],
408 | $lowlimit, $highlimit, $val, $val;
409 | }
410 | else {
411 | printf $csv "%d,0x%04x,0x%02x,%d,%s,%s,%ld,%ld,,,%s\n",
412 | $i, $index, $subindex, $objversion, @datatypename[$datatype], @accessname[$access],
413 | $lowlimit, $highlimit, $str;
414 | }
415 |
416 | # report progress:
417 | print STDOUT "got entry [$i/$diclen]\n";
418 | }
419 |
420 |
421 | # finish:
422 | close $csv;
423 |
424 | print STDOUT "Leaving PREOP mode...\n";
425 | do_cfg("OP") or die "Cannot leave PREOP mode\n";
426 |
427 | print STDOUT "Done.\n";
428 | exit;
429 |
--------------------------------------------------------------------------------
/clients/diagmode/diagcmd.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 | #
3 | # Purpose: Issue OVMS commands via serial line using DIAG mode
4 | # Needs: OVMS firmware with DIAG mode support
5 | # Author: Michael Balzer
6 | # Version: 1.0 (2015/12/25)
7 | # Requires: Win32::SerialPort for Windows or Device::SerialPort for UNIX
8 | #
9 | # Permission is hereby granted, free of charge, to any person obtaining a copy
10 | # of this software and associated documentation files (the "Software"), to deal
11 | # in the Software without restriction, including without limitation the rights
12 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | # copies of the Software, and to permit persons to whom the Software is
14 | # furnished to do so, subject to the following conditions:
15 | #
16 | # The above copyright notice and this permission notice shall be included in
17 | # all copies or substantial portions of the Software.
18 | #
19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | # THE SOFTWARE.
26 | #
27 |
28 | use Getopt::Long;
29 | use Symbol qw( gensym );
30 |
31 |
32 | #
33 | # Command line arguments:
34 | #
35 |
36 | my $help = '';
37 | my $debug = '';
38 | my $init = '';
39 | my $device = '/dev/ttyUSB0';
40 |
41 | GetOptions (
42 | 'help' => \$help,
43 | 'debug' => \$debug,
44 | 'init' => \$init,
45 | 'device=s' => \$device );
46 |
47 | if ($help) {
48 | print STDOUT
49 | "Usage: $0 [options] cmd [cmd...]\n",
50 | "\n",
51 | "Commands can be all OVMS DIAG mode commands.\n",
52 | "Modem commands (AT...) are not yet supported.\n",
53 | "\n",
54 | "Options:\n",
55 | " --help show this help\n",
56 | " --debug enable debug output\n",
57 | " --init initialize OVMS DIAG mode (use on module reset/powerup)\n",
58 | " --device=... specify serial device, default=/dev/ttyUSB0\n",
59 | "\n",
60 | "Examples:\n",
61 | "1. init and get battery status:\n $0 --init \"S STAT\"\n",
62 | "2. get parameters and features:\n $0 \"M 1\" \"M 3\"\n",
63 | "3. get firmware version:\n $0 \"S VERSION\"\n",
64 | "\n";
65 | exit;
66 | }
67 |
68 |
69 | #
70 | # Global variables:
71 | #
72 |
73 | my $line;
74 | my @response;
75 |
76 |
77 | #
78 | # Open serial port
79 | #
80 |
81 | my $handle = gensym();
82 | my $port;
83 |
84 | if ( $^O =~ m/Win32/ ) {
85 | require Win32::SerialPort;
86 | $port = tie (*$handle, "Win32::SerialPort", $device)
87 | or die "Cannot open $device\n";
88 | }
89 | else {
90 | require Device::SerialPort;
91 | $port = tie (*$handle, "Device::SerialPort", $device)
92 | or die "Cannot open $device\n";
93 | }
94 |
95 | $port->user_msg(OFF);
96 | $port->baudrate(9600);
97 | $port->parity("none");
98 | $port->databits(8);
99 | $port->stopbits(1);
100 | $port->handshake("none");
101 |
102 | $port->read_char_time(0);
103 | $port->read_const_time(1000);
104 | $port->are_match("\r","\n");
105 |
106 | $port->write_settings;
107 |
108 | $port->purge_all;
109 |
110 |
111 | #
112 | # wait_for: wait for specific input line with timeout
113 | # removes CR/LF and modem responses (OK/ERROR) from input
114 | # returns undef if timeout occurs
115 | #
116 | sub wait_for {
117 | my $pattern = shift // '.'; # valid input pattern
118 | my $timeout = shift // 10; # read timeout in seconds / 0=no timeout
119 |
120 | my $i = 0;
121 | do {
122 | my $line = <$handle>;
123 |
124 | $i++ if !$line;
125 |
126 | if ($debug) {
127 | print STDOUT ($line ne "") ? "$line\n" : "... [wait $i]\n";
128 | }
129 |
130 | # remove CR/LF and modem responses
131 | $line =~ s/[\r\n]//g;
132 | $line =~ s/^(OK|ERROR)$//g;
133 |
134 | return $line if ($line =~ /$pattern/);
135 |
136 | } while ($timeout == 0 or $i < $timeout);
137 |
138 | return undef;
139 | }
140 |
141 |
142 | #
143 | # do_cmd: send OVMS command, retry on timeout, return response (array of lines)
144 | #
145 | # Example: send a command, read response lines until end tag or timeout:
146 | # @response = do_cmd('S STAT');
147 | #
148 | sub do_cmd {
149 | my $cmd = shift // ''; # empty = don't send, just listen
150 | my $expect = shift // '^# '; # response intro pattern (will be removed from result)
151 | my $timeout = shift // 10; # read timeout in seconds
152 | my $tries = shift // 3; # number of send attempts
153 | my $endtag = shift // '^#.$'; # response end pattern (will be removed from result)
154 |
155 | my @result;
156 | my $line;
157 | my $try;
158 |
159 | print STDOUT "*** COMMAND: $cmd\n" if $debug;
160 |
161 | for ($try = 1; $try <= $tries; $try++) {
162 |
163 | # send command:
164 | if ($cmd ne "") {
165 | print STDERR "*** COMMAND RETRY: $cmd\n" if $try > 1;
166 | print $handle "$cmd\r";
167 | }
168 |
169 | return undef if $expect eq "";
170 |
171 | # wait for response intro:
172 | $line = wait_for($expect, $timeout);
173 |
174 | # retry send on timeout:
175 | next if !defined($line);
176 |
177 | # remove intro pattern:
178 | $line =~ s/$expect//;
179 |
180 | # save response:
181 | push @result, $line;
182 |
183 | # read more lines:
184 | while (($linecnt == 0 || --$linecnt > 0) && defined($line = wait_for('.', 1))) {
185 | print STDOUT "+ $line\n" if $debug;
186 | $line =~ s/$expect//;
187 | last if $line =~ /$endtag/;
188 | push @result, $line;
189 | }
190 |
191 | # done:
192 | return @result;
193 | }
194 |
195 | # no success:
196 | print STDERR "*** COMMAND FAILED: no response for '$cmd'\n";
197 | return undef;
198 | }
199 |
200 |
201 |
202 | #
203 | # do_init: enter OVMS diagnostic mode
204 | #
205 | sub do_init {
206 |
207 | GODIAG: while (1) {
208 | print STDOUT "*** INIT: PLEASE POWER UP OR RESET OVMS MODULE NOW\n";
209 | $line = wait_for('RDY') || next GODIAG;
210 | @response = do_cmd('SETUP', 'OVMS DIAGNOSTICS MODE', 30) && last GODIAG;
211 | }
212 |
213 | print STDOUT "*** INIT: DIAG MODE ESTABLISHED\n";
214 | }
215 |
216 |
217 |
218 | # ====================================================================
219 | # MAIN
220 | # ====================================================================
221 |
222 | do_init if $init;
223 |
224 | for $cmd (@ARGV) {
225 |
226 | @response = do_cmd($cmd);
227 |
228 | for ($i = 0; $i < @response; $i++) {
229 | print STDOUT "@response[$i]\n";
230 | }
231 | }
232 |
233 | exit;
234 |
--------------------------------------------------------------------------------
/clients/edimax/edimax_check.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Read config:
4 | source edimax_config.txt || exit
5 |
6 | # Read current SOC:
7 | SOC=$(./status.pl | sed 's:MP-0 S\([0-9]*\).*:\1:g')
8 | if [[ "$SOC" = "" ]] ; then
9 | logger -p user.error "${0}: SOC level unknown, aborting"
10 | echo "SOC level unknown, aborting" >&2
11 | exit
12 | fi
13 |
14 | # Read current Edimax state:
15 | STATE=$(./edimax_get.sh)
16 | if [[ "$STATE" = "" ]] ; then
17 | logger -p user.error "${0}: Plug state unknown, aborting"
18 | echo "Plug state unknown, aborting" >&2
19 | exit
20 | fi
21 |
22 | # Switch Edimax Smart Plug according to SOC level:
23 | if [[ $SOC -ge $SOC_OFF && $STATE = "ON" ]] ; then
24 | logger "${0}: SOC level ${SOC}%, switching OFF"
25 | echo -n "SOC level ${SOC}%, switching OFF... "
26 | ./edimax_switch.sh OFF
27 | elif [[ $SOC -le $SOC_ON && $STATE = "OFF" ]] ; then
28 | logger "${0}: SOC level ${SOC}%, switching ON"
29 | echo -n "SOC level ${SOC}%, switching ON... "
30 | ./edimax_switch.sh ON
31 | fi
32 |
--------------------------------------------------------------------------------
/clients/edimax/edimax_config.txt:
--------------------------------------------------------------------------------
1 | # Edimax Smart Plug LAN access parameters:
2 | EDIMAX_IP=192.168.1.33
3 | EDIMAX_USER=admin
4 | EDIMAX_PASS=1234
5 | # AUTH: set 'basic' for old firmware, 'digest' for new
6 | EDIMAX_AUTH=basic
7 |
8 | # Switch on Edimax at or below SOC level:
9 | SOC_ON=25
10 |
11 | # Switch off Edimax at or above SOC level:
12 | SOC_OFF=80
13 |
--------------------------------------------------------------------------------
/clients/edimax/edimax_crontab.txt:
--------------------------------------------------------------------------------
1 | ### Edimax control by SOC level ###
2 |
3 | # Send switch on/off infos to:
4 | MAILTO=your@email.org
5 |
6 | # Schedule SOC check every 2 minutes:
7 | # (note: change client path according to your installation)
8 | */2 * * * * cd $HOME/ovms/client && ./edimax_check.sh 2>/dev/null
9 |
--------------------------------------------------------------------------------
/clients/edimax/edimax_get.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Read config:
4 | source edimax_config.txt || exit
5 |
6 | # Get Edimax state:
7 | RESULT=$( ( curl --silent --data @- --${EDIMAX_AUTH:-basic} --user ${EDIMAX_USER}:${EDIMAX_PASS} http://${EDIMAX_IP}:10000/smartplug.cgi --output - <<-__XML__
8 |
9 |
10 | __XML__
11 | ) | sed -e 's:.*\([^<]*\).*:\1:g' )
12 |
13 | # Output result:
14 | echo -n ${RESULT}
15 |
--------------------------------------------------------------------------------
/clients/edimax/edimax_readme.txt:
--------------------------------------------------------------------------------
1 | INTENDED USE
2 | ============
3 |
4 | For vehicles that do not support charge control via CAN: automatically switch on/off charging according to the current SOC level using a home automation plug.
5 |
6 | This can be used to keep the vehicle charged when parking for long periods, and/or to terminate charging below 100%.
7 |
8 |
9 | REQUIREMENTS
10 | ============
11 |
12 | 1. An Edimax Home Automation Smart Plug Series device, i.e.:
13 | - SP-1101W
14 | - SP-2101W
15 | See: http://us.edimax.com/edimax/merchandise/merchandise_list/data/edimax/us/home_automation_smart_plug/
16 |
17 | 2. Some sort of unixoid server running some sort of cron daemon (a Raspberry Pi will do).
18 |
19 | 3. Configured OVMS perl client.
20 |
21 |
22 | CONFIGURATION
23 | =============
24 |
25 | 1. Setup your Edimax as described in the quick setup guide.
26 |
27 | 2. Copy the "edimax_*" files into your OVMS client folder.
28 |
29 | 3. Fill in the Edimax IP address and auth data in "edimax_config.txt"...
30 |
31 | 4. ...and set the SOC_ON and SOC_OFF levels as desired.
32 |
33 | 5. Add the "edimax_check.sh" script to your cron tab, schedule i.e. once per minute (see example crontab).
34 |
35 | That's all.
36 |
37 |
38 | OPERATION
39 | =========
40 |
41 | On each invocation "edimax_check.sh" will read the current SOC level of your car using the "status.pl" script.
42 |
43 | If the SOC level is at or below the SOC_ON threshold configured, the Edimax plug will be switched on. If it's at or above the SOC_OFF level, the plug will be switched off.
44 |
45 | The switching is done by the "edimax_switch.sh" script (expecting "ON" or "OFF" as it's single argument), "edimax_get.sh" outputs the current state.
46 |
47 | To start or stop charging at other SOC levels, you can push the power button at the plug, use the App or "edimax_switch.sh".
48 |
49 |
50 |
--------------------------------------------------------------------------------
/clients/edimax/edimax_switch.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Read config:
4 | source edimax_config.txt || exit
5 |
6 | # Get switch state argument:
7 | TOSTATE=$1
8 | if [[ "${TOSTATE}" = "" ]] ; then
9 | echo "Usage: $0 [ON|OFF]"
10 | exit
11 | fi
12 |
13 | # Switch Edimax and read result:
14 | RESULT=$( ( curl --silent --data @- --${EDIMAX_AUTH:-basic} --user ${EDIMAX_USER}:${EDIMAX_PASS} http://${EDIMAX_IP}:10000/smartplug.cgi --output - <<-__XML__
15 |
16 | ${TOSTATE}
17 | __XML__
18 | ) | sed -e 's:.*\([^<]*\).*:\1:g' )
19 |
20 | # Output result:
21 | echo ${RESULT}
22 |
--------------------------------------------------------------------------------
/clients/homematic/Homematic.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # V 0.1
4 | #
5 | # This is Freeware - Use at your own risk
6 | # Question? freeware@meinbriefkasten.eu or german Twizy-forum http://www.vectrix-forum.de/
7 | #
8 | # Access to homematic using Unix bash
9 | #
10 | # required extension on homematic XML-API V 1.10 (please install, its free)
11 | #
12 |
13 | #
14 | # function HMGetNumber()
15 | # Used to get a number-value of a Homematic device
16 | #
17 | # Parameter:
18 | # IP-Adress of Homematic
19 | # ID of device to be retrieved (get ID using HQ-WebUI extension in homematic)
20 | #
21 | function HMGetNumber()
22 | {
23 | if [[ $# -lt 2 ]]; then
24 | Msg
25 | echo "Usage $0 IP-Adress_Homematic DeviceID"
26 | return 0
27 | fi
28 |
29 | # Get Edimax state:
30 | RESULT=$( ( curl --silent http://$1/addons/xmlapi/state.cgi?datapoint_id=$2 ) | sed -e 's:.*value=.\([0-9]*\).*:\1:g' )
31 |
32 | echo -n $RESULT
33 |
34 | return 0
35 | }
36 |
37 |
38 | #
39 | # function HMGetStringBoolean()
40 | # Used to get a string-value of a Homematic device
41 | #
42 | # Parameter:
43 | # IP-Adress of Homematic
44 | # ID of device to be retrieved (get ID using HQ-WebUI extension in homematic)
45 | #
46 | function HMGetStringBoolean()
47 | {
48 | if [[ $# -lt 2 ]]; then
49 | Msg
50 | echo "Usage $0 IP-Adress_Homematic DeviceID"
51 | return ""
52 | fi
53 |
54 | RESULT=$( ( curl --silent http://$1/addons/xmlapi/state.cgi?datapoint_id=$2 ) | sed -e 's:.*value=.\([truefalse]*\).*:\1:g' )
55 |
56 | echo -n $RESULT
57 |
58 | return 0
59 | }
60 |
61 |
62 | #
63 | # function HMSwitch()
64 | # Used to get a string-value of a Homematic device
65 | #
66 | # Parameter:
67 | # IP-Adress of Homematic
68 | # ID of device to be switched (get ID using HQ-WebUI extension in homematic)
69 | # new value (false = turned off, true = turned on)
70 | #
71 | function HMSwitch()
72 | {
73 | if [[ $# -lt 3 ]]; then
74 | echo "Usage $0 IP-Adress_Homematic DeviceID newValue[true,false]"
75 | return ""
76 | fi
77 |
78 | TOSTATE=$3
79 | if [[ "${TOSTATE}" = "" ]] ; then
80 | echo "Usage $0 IP-Adress_Homematic DeviceID newValue[true,false]"
81 | return ""
82 | fi
83 |
84 | # Switch Homematic and read result:
85 | RESULT=$( ( curl --silent http://$1/addons/xmlapi/statechange.cgi?ise_id=$2\&new_value=$TOSTATE ) | sed -e 's:.*new_value=.\([truefalse]*\).*:\1:g' )
86 |
87 | echo -n $RESULT
88 |
89 | return 0
90 | }
91 |
92 |
93 |
--------------------------------------------------------------------------------
/clients/homematic/README_HOMEMATIC:
--------------------------------------------------------------------------------
1 | INTENDED USE
2 | ============
3 |
4 | For vehicles that do not support charge control via CAN: automatically switch on/off charging according to the current SOC level using a homematic plug.
5 |
6 | This can be used to keep the vehicle charged when parking for long periods, and/or to terminate charging below 100%.
7 |
8 |
9 | REQUIREMENTS
10 | ============
11 |
12 | 1. An running Homematic Homeautomation system with installed extension
13 | - XML-API V 1.10 (required)
14 | - HQ-WebUI (recommended)
15 | ==> http://www.homematic.com/
16 |
17 | 2. Configured OVMS perl client.
18 |
19 | 3. Some sort of server running some sort of cron daemon.
20 |
21 |
22 | CONFIGURATION
23 | =============
24 |
25 | 1. Setup your Homematic.
26 | - install XML-API 1.10
27 | - recommended for ID-retrieval HQ_WEBUI
28 | - configure systemvariable for BatteryUpperThreshold and BatteryLowerThreshold (number 0-100)
29 | - Fill in plausible values for BatteryUpperThreshold and BatteryLowerThreshold (Upper = 100, Lower = 90)
30 |
31 | 2. configuration config.txt
32 | - IP of homematic
33 | - Device IDs in homematic
34 | - GPS-coordinates of home (see config.txt for more explanations on how to get correct coordinates)
35 |
36 | 3. Add the "check.sh" script to your cron tab, schedule i.e. once per minute or every 3-5 minutes. (see example crontab).
37 |
38 | That's all.
39 |
40 |
41 | OPERATION
42 | =========
43 |
44 | On each invocation "check.sh" will read the current SOC level of your car using the "status.pl" script.
45 |
46 | If car is at home and the SOC level is at or below the BatteryLowerThreshhold threshold configured, the Homematic plug will be switched on. If it's at or above the BatteryUpperThreshhold level, the plug will be switched off.
47 | If the cars leaves home before BatteryUpperThreshold is reached the plug will be switched off.
48 |
49 | The switching is done by the HM_Switch function in "Homematic.sh" script (expecting "true" or "false" as it's single argument), for more details look into the scripts, detailed explanation is provided there
50 |
51 |
52 |
--------------------------------------------------------------------------------
/clients/homematic/check.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # V 0.1
4 | #
5 | # This is Freeware - Use at your own risk
6 | # Question? freeware@meinbriefkasten.eu or german Twizy-forum http://www.vectrix-forum.de/
7 | #
8 | # Check if vehicle is home and chage is less than defined threshold
9 | # if yes switch on powerplug to charge
10 | # if no switch of powerplug to charge
11 | # if vehicle leaves home before fully charged switch of powerplug
12 | #
13 | # Coordinated and IDs für Homematic are to be defined in config.txt
14 | #
15 |
16 | now=$(date +"%Y%m%d")
17 | LogFile="/tmp/${now}_ovms.log"
18 | /usr/bin/touch "$LogFile"
19 | echo "--------------------------------------" | tee -a $LogFile
20 |
21 | # Log current dateTime
22 | echo "$(/bin/date) - Check started" | tee -a $LogFile
23 |
24 | # Read Configuration (Home coordinates, homematic device IDs)
25 | source config.txt || exit
26 |
27 | # import Homematic-functions
28 | source Homematic.sh || exit
29 |
30 | # Read thresholds für SOC
31 | SOC_LowerThreshold=$(HMGetNumber $HOMEMATIC_IP $AccuLowerThreshold_ID)
32 | echo "SOC Lower Threshold Schwellwert = ${SOC_LowerThreshold}%" | tee -a $LogFile
33 |
34 | SOC_UpperThreshold=$(HMGetNumber $HOMEMATIC_IP $AccuUpperThreshold_ID )
35 | echo "SOC Upper Threshold = ${SOC_UpperThreshold}%" | tee -a $LogFile
36 |
37 | # Read current SOC:
38 | SOC=$(./status.pl | sed 's:MP-0 S\([0-9]*\).*:\1:g')
39 | echo "SOC current charge = ${SOC}%" | tee -a $LogFile
40 |
41 | # Read current Homematic switch state:
42 | STATE=$(HMGetStringBoolean $HOMEMATIC_IP $SwitchTwizy_ID)
43 | echo "Homematic current Switch state = ${STATE}" | tee -a $LogFile
44 |
45 | # READ TWIZY Location (Result is home or ontheroad)
46 | LOCATION=$(./location.sh)
47 | echo "Current location of vehicle = ${LOCATION}" | tee -a $LogFile
48 |
49 | if [[ $LOCATION = "home" ]] ; then
50 | # Switch Homematic Twizy Switch according to SOC level:
51 | if [[ $SOC -ge $SOC_UpperThreshold && $STATE = "true" ]] ; then
52 | echo "SOC level ${SOC}%, switching OFF... "| tee -a $LogFile
53 | $(HMSwitch $HOMEMATIC_IP $SwitchTwizy_ID false)
54 | elif [[ $SOC -le $SOC_LowerThreshold && $STATE = "false" ]] ; then
55 | echo "SOC level ${SOC}%, switching ON... "| tee -a $LogFile
56 | $(HMSwitch $HOMEMATIC_IP $SwitchTwizy_ID true)
57 | fi
58 | else
59 | if [[ $STATE = "true" ]] ; then
60 | echo "Turn off switch" i| tee -a $LogFile
61 | $(HMSwitch $HOMEMATIC_IP $SwitchTwizy_ID false)
62 | fi
63 | fi
64 |
--------------------------------------------------------------------------------
/clients/homematic/config.txt:
--------------------------------------------------------------------------------
1 | #
2 | # V 0.1
3 | #
4 | # This is Freeware - Use at your own risk
5 | # Question? freeware#meinbriefkasten.eu or gherman Twizy-forum http://www.vectrix-forum.de/
6 | #
7 | # Homematic access parameters:
8 | # Get DeviceIDs using HQ-WebUI extension in homematic
9 | #
10 | # AccuUpperThreshold_ID and AccuLowerThreshold_ID
11 | # these are 2 number variables [0 - 100] on homematic
12 | # if charge of vehicle drops below LowerThreshold, the powerplug will be switched on
13 | # if charge of vehicle reaches UpperThreshold, the powerplug will be switched off
14 | #
15 | HOMEMATIC_IP=192.168.3.4
16 | SwitchTwizy_ID=2797
17 | AccuUpperThreshold_ID=7865
18 | AccuLowerThreshold_ID=7864
19 |
20 | #
21 | # GIS Koordinates of Home
22 | # use google maps to get coordinates.
23 | # Browse to the place you call home click right and select What is here?
24 | # (or similar, sorry only have a german version "Was ist hier?")
25 | # Example coordinates are Berlin Reichstag
26 | #
27 | HOME_GIS_LONG=52.518591
28 | HOME_GIS_LAT=13.376171
29 |
--------------------------------------------------------------------------------
/clients/homematic/homematic_crontab.txt:
--------------------------------------------------------------------------------
1 | ### Edimax control by SOC level ###
2 |
3 | # Send switch on/off infos to:
4 | MAILTO=your@email.org
5 |
6 | # Schedule SOC check very 3 minutes:
7 | # (note: change client path according to your installation)
8 | */3 * * * * cd $HOME/ovms/client && ./check.sh
9 |
--------------------------------------------------------------------------------
/clients/homematic/location.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # V 0.1
4 | #
5 | # This is Freeware - Use at your own risk
6 | # Question? freeware@meinbriefkasten.eu or german Twizy-forum http://www.vectrix-forum.de/
7 | #
8 | # INTENDED USE
9 | # Checks if our vehicle is at home. Home is defined as to be in 50m of the configured coordinates
10 | #
11 | # Returnvalue:
12 | # "home" - if Twizy is at home
13 | # "ontheroad" - if twizy is not at home
14 | #
15 | # required Inputparameters
16 | # in config.txt the GPS coordinated of the place you call home need to be defined
17 | #
18 |
19 |
20 | # Read config:
21 | source config.txt || exit
22 |
23 | # Read current Location
24 | LOC_MSG=$(./status.pl L)
25 | LONG=$(echo $LOC_MSG | sed 's:MP-0 L\([0-9.-]*\).*:\1:g')
26 | LAT=$(echo $LOC_MSG | sed 's:MP-0 L[0-9.-]*,\([0-9.-]*\).*:\1:g')
27 |
28 | IS_HOME=$(calc "( abs($LONG - $HOME_GIS_LONG) < 0.0004 ) && ( abs($LAT - $HOME_GIS_LAT) < 0.0004 )")
29 |
30 | if [[ "$IS_HOME" -eq 1 ]] ; then
31 | RESULT="home"
32 | else
33 | RESULT="ontheroad"
34 | fi
35 |
36 | # Output result:
37 | echo -n $RESULT
38 |
--------------------------------------------------------------------------------
/clients/notify.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 |
3 | use Digest::MD5;
4 | use Digest::HMAC;
5 | use Crypt::RC4::XS;
6 | use MIME::Base64;
7 | use IO::Socket::INET;
8 | use Config::IniFiles;
9 |
10 | # Read command line arguments:
11 | my $cmd_text = $ARGV[0];
12 |
13 | if (!$cmd_text) {
14 | print "usage: notify.pl text\n";
15 | exit;
16 | }
17 |
18 | my $b64tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
19 |
20 | # Configuration
21 | my $config = Config::IniFiles->new(-file => 'ovms_client.conf');
22 |
23 | my $vehicle_id = $config->val('client','vehicle_id','TESTCAR');
24 | my $server_password = $config->val('client','server_password','NETPASS');
25 | my $module_password = $config->val('client','module_password','OVMS');
26 |
27 | my $server_ip = $config->val('client','server_ip','54.197.255.127');
28 |
29 |
30 | #####
31 | ##### CONNECT
32 | #####
33 |
34 | my $sock = IO::Socket::INET->new(PeerAddr => $server_ip,
35 | PeerPort => '6867',
36 | Proto => 'tcp');
37 |
38 | #####
39 | ##### REGISTER
40 | #####
41 |
42 | rand;
43 | my $client_token;
44 | foreach (0 .. 21)
45 | { $client_token .= substr($b64tab,rand(64),1); }
46 |
47 | my $client_hmac = Digest::HMAC->new($server_password, "Digest::MD5");
48 | $client_hmac->add($client_token);
49 | my $client_digest = $client_hmac->b64digest();
50 |
51 | # Register as batch client (type "B"):
52 | print $sock "MP-B 0 $client_token $client_digest $vehicle_id\r\n";
53 |
54 | my $line = <$sock>;
55 | chop $line;
56 | chop $line;
57 | my ($welcome,$crypt,$server_token,$server_digest) = split /\s+/,$line;
58 |
59 | my $d_server_digest = decode_base64($server_digest);
60 | my $client_hmac = Digest::HMAC->new($server_password, "Digest::MD5");
61 | $client_hmac->add($server_token);
62 | if ($client_hmac->digest() ne $d_server_digest)
63 | {
64 | print STDERR " Client detects server digest is invalid - aborting\n";
65 | exit(1);
66 | }
67 |
68 | $client_hmac = Digest::HMAC->new($server_password, "Digest::MD5");
69 | $client_hmac->add($server_token);
70 | $client_hmac->add($client_token);
71 | my $client_key = $client_hmac->digest;
72 |
73 | my $txcipher = Crypt::RC4::XS->new($client_key);
74 | $txcipher->RC4(chr(0) x 1024); # Prime the cipher
75 | my $rxcipher = Crypt::RC4::XS->new($client_key);
76 | $rxcipher->RC4(chr(0) x 1024); # Prime the cipher
77 |
78 |
79 | #####
80 | ##### SEND COMMAND
81 | #####
82 |
83 | my $cmd = "PA".$cmd_text;
84 |
85 | my $encrypted = encode_base64($txcipher->RC4("MP-0 ".$cmd),'');
86 | print $sock "$encrypted\r\n";
87 |
88 |
89 | #####
90 | ##### READ RESPONSE
91 | #####
92 |
93 | my $ptoken = "";
94 | my $pdigest = "";
95 | my $data = "";
96 | my $discardcnt = 0;
97 |
98 | while(1)
99 | {
100 | # Timeout socket reads after 1 second:
101 | eval {
102 | local $SIG{ALRM} = sub { die "Timed Out" };
103 | alarm 1;
104 | $data = <$sock>;
105 | alarm 0;
106 | };
107 | if ($@ and $@ =~ /Timed Out/) {
108 | print STDOUT "OK.\n";
109 | exit;
110 | }
111 | }
112 |
113 |
--------------------------------------------------------------------------------
/clients/ovms_client.conf:
--------------------------------------------------------------------------------
1 | [client]
2 | vehicle_id=TESTCAR
3 | server_password=NETPASS
4 | module_password=OVMS
5 | server_ip=54.197.255.127
6 |
--------------------------------------------------------------------------------
/clients/querybalance.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | echo "Query SIM account balance..."
3 | ./cmd.pl 41 "*100#"
4 |
--------------------------------------------------------------------------------
/clients/rt_fetchdata.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | NAME=$1
4 |
5 | if test "${NAME}" == "" ; then
6 | echo "Usage: $0 basename"
7 | echo "e.g. $0 130311-tour1"
8 | exit
9 | fi
10 |
11 | # common protocol record intro columns:
12 | MPHEAD="mp_cmd,mp_cmdres,mp_row,mp_rowcnt,rec_type,rec_time"
13 |
14 | echo "Fetch *-OVM-DebugCrash..."
15 | (
16 | HEAD="${MPHEAD},0,version,crashcnt,crashreason,checkpoint"
17 | echo $HEAD
18 | ./cmd.pl 32 "*-OVM-DebugCrash"
19 | ) > "${NAME}-debugcrash.csv"
20 |
21 | echo "Fetch RT-BAT-P..."
22 | (
23 | HEAD="${MPHEAD},packnr,volt_alertstatus,temp_alertstatus"
24 | HEAD+=",soc,soc_min,soc_max"
25 | HEAD+=",volt_act,volt_min,volt_max"
26 | HEAD+=",temp_act,temp_min,temp_max"
27 | HEAD+=",cell_volt_stddev_max,cmod_temp_stddev_max"
28 | HEAD+=",max_drive_pwr,max_recup_pwr"
29 | echo $HEAD
30 | ./cmd.pl 32 "RT-BAT-P"
31 | ) > "${NAME}-battpack.csv"
32 |
33 | echo "Fetch RT-BAT-C..."
34 | (
35 | HEAD="${MPHEAD},cellnr,volt_alertstatus,temp_alertstatus"
36 | HEAD+=",volt_act,volt_min,volt_max,volt_maxdev"
37 | HEAD+=",temp_act,temp_min,temp_max,temp_maxdev"
38 | echo $HEAD
39 | ./cmd.pl 32 "RT-BAT-C"
40 | ) > "${NAME}-battcell.csv"
41 |
42 | echo "Fetch RT-PWR-Stats..."
43 | (
44 | HEAD="${MPHEAD},0"
45 | HEAD+=",speed_const_dist,speed_const_use,speed_const_rec"
46 | HEAD+=",speed_const_cnt,speed_const_sum"
47 | HEAD+=",speed_accel_dist,speed_accel_use,speed_accel_rec"
48 | HEAD+=",speed_accel_cnt,speed_accel_sum"
49 | HEAD+=",speed_decel_dist,speed_decel_use,speed_decel_rec"
50 | HEAD+=",speed_decel_cnt,speed_decel_sum"
51 | HEAD+=",level_up_dist,level_up_hsum,level_up_use,level_up_rec"
52 | HEAD+=",level_down_dist,level_down_hsum,level_down_use,level_down_rec"
53 | HEAD+=",charge_used,charge_recd"
54 | echo $HEAD
55 | ./cmd.pl 32 "RT-PWR-Stats"
56 | ) > "${NAME}-stats.csv"
57 |
58 | echo "Fetch RT-GPS-Log..."
59 | (
60 | HEAD="${MPHEAD},odometer_mi_10th,latitude,longitude,altitude,direction,speed"
61 | HEAD+=",gps_fix,gps_stale_cnt,gsm_signal"
62 | HEAD+=",current_power_w,power_used_wh,power_recd_wh,power_distance,min_power_w,max_power_w"
63 | HEAD+=",car_status"
64 | HEAD+=",max_drive_pwr_w,max_recup_pwr_w"
65 | HEAD+=",autodrive_level,autorecup_level"
66 | HEAD+=",min_current_a,max_current_a"
67 | echo $HEAD
68 | ./cmd.pl 32 "RT-GPS-Log"
69 | ) > "${NAME}-gpslog.csv"
70 |
71 | echo "Fetch RT-PWR-Log..."
72 | (
73 | HEAD="${MPHEAD},0"
74 | HEAD+=",odometer_km,latitude,longitude,altitude"
75 | HEAD+=",chargestate,parktime_min"
76 | HEAD+=",soc,soc_min,soc_max"
77 | HEAD+=",power_used_wh,power_recd_wh,power_distance"
78 | HEAD+=",range_estim_km,range_ideal_km"
79 | HEAD+=",batt_volt,batt_volt_min,batt_volt_max"
80 | HEAD+=",batt_temp,batt_temp_min,batt_temp_max"
81 | HEAD+=",motor_temp,pem_temp"
82 | HEAD+=",trip_length_km,trip_soc_usage"
83 | HEAD+=",trip_avg_speed_kph,trip_avg_accel_kps,trip_avg_decel_kps"
84 | HEAD+=",charge_used_ah,charge_recd_ah,batt_capacity_prc,charger_temp"
85 | echo $HEAD
86 | ./cmd.pl 32 "RT-PWR-Log"
87 | ) > "${NAME}-pwrlog.csv"
88 |
89 | echo "Done."
90 |
91 | ls -l ${NAME}-*
92 |
--------------------------------------------------------------------------------
/clients/rt_fetchlogs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | NAME=$1
4 |
5 | if test "${NAME}" == "" ; then
6 | echo "Usage: $0 basename"
7 | echo "e.g. $0 140118-test"
8 | exit
9 | fi
10 |
11 | # common protocol record intro columns:
12 | MPHEAD="mp_cmd,mp_cmdres,mp_row,mp_rowcnt,rec_type,rec_time"
13 |
14 | echo "Fetch RT-ENG-LogKeyTime..."
15 | (
16 | echo "${MPHEAD},0,KeyHour,KeyMinSec"
17 | ./cmd.pl 32 "RT-ENG-LogKeyTime"
18 | ) > "${NAME}-LogKeyTime.csv"
19 |
20 | echo "Fetch RT-ENG-LogAlerts..."
21 | (
22 | echo "${MPHEAD},EntryNr,Code,Description"
23 | ./cmd.pl 32 "RT-ENG-LogAlerts"
24 | ) > "${NAME}-LogAlerts.csv"
25 |
26 | echo "Fetch RT-ENG-LogFaults..."
27 | (
28 | echo "${MPHEAD},EntryNr,Code,Description,TimeHour,TimeMinSec,Data1,Data2,Data3"
29 | ./cmd.pl 32 "RT-ENG-LogFaults"
30 | ) > "${NAME}-LogFaults.csv"
31 |
32 | echo "Fetch RT-ENG-LogSystem..."
33 | (
34 | echo "${MPHEAD},EntryNr,Code,Description,TimeHour,TimeMinSec,Data1,Data2,Data3"
35 | ./cmd.pl 32 "RT-ENG-LogSystem"
36 | ) > "${NAME}-LogSystem.csv"
37 |
38 | echo "Fetch RT-ENG-LogCounts..."
39 | (
40 | echo -n "${MPHEAD},EntryNr,Code,Description,LastTimeHour,LastTimeMinSec"
41 | echo ",FirstTimeHour,FirstTimeMinSec,Count"
42 | ./cmd.pl 32 "RT-ENG-LogCounts"
43 | ) > "${NAME}-LogCounts.csv"
44 |
45 | echo "Fetch RT-ENG-LogMinMax..."
46 | (
47 | echo -n "${MPHEAD},EntryNr,BatteryVoltageMin,BatteryVoltageMax"
48 | echo -n ",CapacitorVoltageMin,CapacitorVoltageMax,MotorCurrentMin,MotorCurrentMax"
49 | echo ",MotorSpeedMin,MotorSpeedMax,DeviceTempMin,DeviceTempMax"
50 | ./cmd.pl 32 "RT-ENG-LogMinMax"
51 | ) > "${NAME}-LogMinMax.csv"
52 |
53 | echo "Done."
54 |
55 | ls -l ${NAME}-*
56 |
--------------------------------------------------------------------------------
/clients/rt_querylogs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo "Query RT-ENG-LogAlerts..."
4 | ./cmd.pl 210 1
5 |
6 | echo "Query RT-ENG-LogFaults..."
7 | ./cmd.pl 210 2,0
8 | ./cmd.pl 210 2,10
9 | ./cmd.pl 210 2,20
10 | ./cmd.pl 210 2,30
11 |
12 | echo "Query RT-ENG-LogSystem..."
13 | ./cmd.pl 210 3,0
14 | ./cmd.pl 210 3,10
15 |
16 | echo "Query RT-ENG-LogCounts..."
17 | ./cmd.pl 210 4
18 |
19 | echo "Query RT-ENG-LogMinMax..."
20 | ./cmd.pl 210 5
21 |
22 | echo "Done."
23 | echo "Hint: use fetchlogs.sh to retrieve logs now."
24 |
--------------------------------------------------------------------------------
/clients/rt_resetlogs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo "Reset RT-ENG-Log*..."
4 | ./cmd.pl 209 99
5 |
6 | echo "Done."
7 |
--------------------------------------------------------------------------------
/clients/serverlog.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | ./cmd.pl 32 "*-OVM-ServerLogs"
3 |
--------------------------------------------------------------------------------
/clients/status.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 |
3 | use Digest::MD5;
4 | use Digest::HMAC;
5 | use Crypt::RC4::XS;
6 | use MIME::Base64;
7 | use IO::Socket::INET;
8 | use Config::IniFiles;
9 | use Getopt::Long;
10 |
11 |
12 | #
13 | # Command line arguments:
14 | #
15 |
16 | my $help = '';
17 | my $retrieve = '';
18 |
19 | GetOptions (
20 | 'help|h' => \$help,
21 | 'retrieve|r' => \$retrieve );
22 |
23 | if ($help) {
24 | print STDOUT
25 | "Usage: $0 [options] [msgcode]\n",
26 | "\n",
27 | "Get message status from server (default) or car (retrieve).\n",
28 | "Message code defaults to 'S'.\n",
29 | "\n",
30 | "Options:\n",
31 | " --help|-h show this help\n",
32 | " --retrieve|-r retrieve update from car module\n",
33 | "\n";
34 | exit;
35 | }
36 |
37 | my $cmd_code = $ARGV[0];
38 |
39 | if (!$cmd_code) {
40 | # Default: "S"tatus message
41 | $cmd_code = "S";
42 | }
43 |
44 |
45 | #
46 | # Read configuration
47 | #
48 |
49 | my $config = Config::IniFiles->new(-file => 'ovms_client.conf');
50 |
51 | my $vehicle_id = $config->val('client','vehicle_id','TESTCAR');
52 | my $server_password = $config->val('client','server_password','NETPASS');
53 | my $module_password = $config->val('client','module_password','OVMS');
54 |
55 | my $server_ip = $config->val('client','server_ip','54.197.255.127');
56 |
57 |
58 | #####
59 | ##### CONNECT
60 | #####
61 |
62 | my $sock = IO::Socket::INET->new(PeerAddr => $server_ip,
63 | PeerPort => '6867',
64 | Proto => 'tcp');
65 |
66 | #####
67 | ##### REGISTER
68 | #####
69 |
70 | my $b64tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
71 |
72 | rand;
73 | my $client_token;
74 | foreach (0 .. 21)
75 | { $client_token .= substr($b64tab,rand(64),1); }
76 |
77 | my $client_hmac = Digest::HMAC->new($server_password, "Digest::MD5");
78 | $client_hmac->add($client_token);
79 | my $client_digest = $client_hmac->b64digest();
80 |
81 | my $skipcnt;
82 | if ($retrieve)
83 | {
84 | # Register as active client (type "A"):
85 | print $sock "MP-A 0 $client_token $client_digest $vehicle_id\r\n";
86 | $skipcnt = 1; # skip server status, wait for car status
87 | }
88 | else
89 | {
90 | # Register as batch client (type "B"):
91 | print $sock "MP-B 0 $client_token $client_digest $vehicle_id\r\n";
92 | $skipcnt = 0; # use server status
93 | }
94 |
95 | my $line = <$sock>;
96 | chop $line;
97 | chop $line;
98 | my ($welcome,$crypt,$server_token,$server_digest) = split /\s+/,$line;
99 |
100 | my $d_server_digest = decode_base64($server_digest);
101 | my $client_hmac = Digest::HMAC->new($server_password, "Digest::MD5");
102 | $client_hmac->add($server_token);
103 | if ($client_hmac->digest() ne $d_server_digest)
104 | {
105 | print STDERR " Client detects server digest is invalid - aborting\n";
106 | exit(1);
107 | }
108 |
109 | $client_hmac = Digest::HMAC->new($server_password, "Digest::MD5");
110 | $client_hmac->add($server_token);
111 | $client_hmac->add($client_token);
112 | my $client_key = $client_hmac->digest;
113 |
114 | my $txcipher = Crypt::RC4::XS->new($client_key);
115 | $txcipher->RC4(chr(0) x 1024); # Prime the cipher
116 | my $rxcipher = Crypt::RC4::XS->new($client_key);
117 | $rxcipher->RC4(chr(0) x 1024); # Prime the cipher
118 |
119 |
120 | #####
121 | ##### READ RESPONSE
122 | #####
123 |
124 | my $ptoken = "";
125 | my $pdigest = "";
126 | my $data = "";
127 | my $discardcnt = 0;
128 |
129 | while(1)
130 | {
131 | # Timeout socket reads after 10 seconds:
132 | eval {
133 | local $SIG{ALRM} = sub { die "Timed Out" };
134 | alarm 10;
135 | $data = <$sock>;
136 | alarm 0;
137 | };
138 | if ($@ and $@ =~ /Timed Out/) {
139 | print STDERR "No more results, exit.\n";
140 | exit;
141 | }
142 |
143 | chop $data; chop $data;
144 | my $decoded = $rxcipher->RC4(decode_base64($data));
145 |
146 | if ($decoded =~ /^MP-0 ET(.+)/) {
147 | $ptoken = $1;
148 | my $paranoid_hmac = Digest::HMAC->new($module_password, "Digest::MD5");
149 | $paranoid_hmac->add($ptoken);
150 | $pdigest = $paranoid_hmac->digest;
151 | # discard:
152 | $decoded = "";
153 | }
154 | elsif ($decoded =~ /^MP-0 EM(.)(.*)/) {
155 | my ($code,$data) = ($1,$2);
156 | my $pmcipher = Crypt::RC4::XS->new($pdigest);
157 | $pmcipher->RC4(chr(0) x 1024); # Prime the cipher
158 | $decoded = $pmcipher->RC4(decode_base64($data));
159 | # reformat as std msg:
160 | $decoded = "MP-0 ".$code.$decoded;
161 | }
162 |
163 | if ($decoded =~ /^MP-0 $cmd_code/) {
164 | if ($skipcnt eq 0) {
165 | print STDOUT $decoded,"\n";
166 | exit;
167 | } else {
168 | $skipcnt--;
169 | }
170 | }
171 | else {
172 | # exit if more than 25 other msgs received in series (assuming timeout):
173 | $discardcnt++;
174 | if ($discardcnt > 25) {
175 | print STDERR "No more results, exit.\n";
176 | exit;
177 | }
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/clients/subscribe.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 |
3 | use Digest::MD5;
4 | use Digest::HMAC;
5 | use Crypt::RC4::XS;
6 | use MIME::Base64;
7 | use IO::Socket::INET;
8 | use Config::IniFiles;
9 |
10 | # Read command line arguments:
11 | my $cmd_user = $ARGV[0];
12 | my $cmd_email = $ARGV[1];
13 |
14 | if (!$cmd_email) {
15 | print "usage: subscribe.pl user email\n";
16 | print "use email '-' to unsubscribe\n";
17 | exit;
18 | }
19 |
20 | my $b64tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
21 |
22 | # Configuration
23 | my $config = Config::IniFiles->new(-file => 'ovms_client.conf');
24 |
25 | my $vehicle_id = $config->val('client','vehicle_id','TESTCAR');
26 | my $server_password = $config->val('client','server_password','NETPASS');
27 | my $module_password = $config->val('client','module_password','OVMS');
28 |
29 | my $server_ip = $config->val('client','server_ip','54.197.255.127');
30 |
31 |
32 | #####
33 | ##### CONNECT
34 | #####
35 |
36 | my $sock = IO::Socket::INET->new(PeerAddr => $server_ip,
37 | PeerPort => '6867',
38 | Proto => 'tcp');
39 |
40 | #####
41 | ##### REGISTER
42 | #####
43 |
44 | rand;
45 | my $client_token;
46 | foreach (0 .. 21)
47 | { $client_token .= substr($b64tab,rand(64),1); }
48 |
49 | my $client_hmac = Digest::HMAC->new($server_password, "Digest::MD5");
50 | $client_hmac->add($client_token);
51 | my $client_digest = $client_hmac->b64digest();
52 |
53 | # Register as batch client (type "B"):
54 | print $sock "MP-B 0 $client_token $client_digest $vehicle_id\r\n";
55 |
56 | my $line = <$sock>;
57 | chop $line;
58 | chop $line;
59 | my ($welcome,$crypt,$server_token,$server_digest) = split /\s+/,$line;
60 |
61 | my $d_server_digest = decode_base64($server_digest);
62 | my $client_hmac = Digest::HMAC->new($server_password, "Digest::MD5");
63 | $client_hmac->add($server_token);
64 | if ($client_hmac->digest() ne $d_server_digest)
65 | {
66 | print STDERR " Client detects server digest is invalid - aborting\n";
67 | exit(1);
68 | }
69 |
70 | $client_hmac = Digest::HMAC->new($server_password, "Digest::MD5");
71 | $client_hmac->add($server_token);
72 | $client_hmac->add($client_token);
73 | my $client_key = $client_hmac->digest;
74 |
75 | my $txcipher = Crypt::RC4::XS->new($client_key);
76 | $txcipher->RC4(chr(0) x 1024); # Prime the cipher
77 | my $rxcipher = Crypt::RC4::XS->new($client_key);
78 | $rxcipher->RC4(chr(0) x 1024); # Prime the cipher
79 |
80 |
81 | #####
82 | ##### SEND COMMAND
83 | #####
84 |
85 | my $cmd = "p".$cmd_user.",mail,,".$vehicle_id.",".$server_password.",".$cmd_email;
86 |
87 | my $encrypted = encode_base64($txcipher->RC4("MP-0 ".$cmd),'');
88 | print $sock "$encrypted\r\n";
89 |
90 |
91 | #####
92 | ##### READ RESPONSE
93 | #####
94 |
95 | my $ptoken = "";
96 | my $pdigest = "";
97 | my $data = "";
98 | my $discardcnt = 0;
99 |
100 | while(1)
101 | {
102 | # Timeout socket reads after 1 second:
103 | eval {
104 | local $SIG{ALRM} = sub { die "Timed Out" };
105 | alarm 1;
106 | $data = <$sock>;
107 | alarm 0;
108 | };
109 | if ($@ and $@ =~ /Timed Out/) {
110 | print STDOUT "OK.\n";
111 | exit;
112 | }
113 | }
114 |
115 |
--------------------------------------------------------------------------------
/docs/OVMS_Protocol.docx:
--------------------------------------------------------------------------------
1 | This file has been moved to a new repository:
2 |
3 | https://docs.openvehicles.com/en/latest/protocol_v2/index.html
4 |
--------------------------------------------------------------------------------
/docs/OVMS_Protocol.pdf:
--------------------------------------------------------------------------------
1 | This file has been moved to a new repository:
2 |
3 | https://docs.openvehicles.com/en/latest/protocol_v2/index.html
4 |
--------------------------------------------------------------------------------
/drupal/images/delete.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openvehicles/Open-Vehicle-Server/86bfddfac80794656a54bd8fd0a9e49cdcf2e364/drupal/images/delete.gif
--------------------------------------------------------------------------------
/drupal/images/edit.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openvehicles/Open-Vehicle-Server/86bfddfac80794656a54bd8fd0a9e49cdcf2e364/drupal/images/edit.gif
--------------------------------------------------------------------------------
/drupal/images/key.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openvehicles/Open-Vehicle-Server/86bfddfac80794656a54bd8fd0a9e49cdcf2e364/drupal/images/key.gif
--------------------------------------------------------------------------------
/drupal/images/provision.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openvehicles/Open-Vehicle-Server/86bfddfac80794656a54bd8fd0a9e49cdcf2e364/drupal/images/provision.png
--------------------------------------------------------------------------------
/drupal/openvehicles.info:
--------------------------------------------------------------------------------
1 | name = Open Vehicles
2 | description = Provides an interface to allow users to view and maintain their open vehicles
3 | core = 7.x
4 |
5 | files[] = openvehicles.module
--------------------------------------------------------------------------------
/v3/README:
--------------------------------------------------------------------------------
1 | Open Vehicle Monitoring System
2 | ==============================
3 |
4 |
5 | The Server
6 | ==========
7 |
8 | The server is a simple open source server which talks two protocols - one to the car and the other to
9 | the cellphone App.
10 |
11 | The car protocol is built on TCP using encrypted communication packets. See the documentation
12 | directory on github for further details on the protocol itself.
13 |
14 | The drupal server also includes a web interface for basic functions such as setting the password,
15 | registering the car and checking status (see 'drupal' directory in github).
16 |
17 | To deploy an OVMS server, you will need the ovms_server.pl script, the ovms_server.conf configuration
18 | file suitably configured, the ovms_server.sql database tables in a mysql database, and optionally
19 | the *.vece files (to convert error code to messages for different vehicle types).
20 |
21 | The other scripts are purely for development and testing purposes.
22 |
23 |
24 | Android Push Notifications
25 | ==========================
26 |
27 | ...are based on Firebase Cloud Messaging (FCM), which is part of the Google Cloud Services.
28 |
29 | Special CPAN modules needed:
30 | - Net::SSLeay -- Perl bindings for OpenSSL and LibreSSL by Chris Novakovic
31 | [https://metacpan.org/dist/Net-SSLeay]
32 | - WWW::FCM::HTTP::V1 -- FCM library by Yui Takeuchi
33 | [https://metacpan.org/pod/WWW::FCM::HTTP::V1]
34 |
35 | To be able to send push notifications to Android devices, you need to register a Firebase project.
36 |
37 | Creating a new Firebase project automatically creates a new Google Cloud project. If you already
38 | have a Cloud project, you can simply add a Firebase project to that.
39 | (About Firebase & Cloud projects: https://firebase.google.com/docs/projects/learn-more)
40 |
41 | Guide: https://firebase.google.com/docs/android/setup
42 |
43 | Create the Firebase project:
44 |
45 | - Log in to the Firebase Console: https://console.firebase.google.com/
46 | - Add project
47 | - select an existing Cloud project to extend, or enter a new name
48 | - disable Analytics when asked
49 | - Note the project number (this is your sender ID)
50 |
51 | Register the App:
52 |
53 | - On the project's main page, click on the Android icon to add an App
54 | - Enter package name: com.openvehicles.OVMS
55 | - leave nickname & SHA sum blank
56 | - Click "Continue" until getting back to the dashboard
57 |
58 | Create a server key:
59 |
60 | - Open the project settings from the menu or via the App icon
61 | - Switch to the "Service Accounts" tab
62 | - Click "Generate new private key"
63 |
64 | Copy the downloaded key file into your OVMS server's "conf" directory, then add…
65 |
66 | api_key_file=conf/
67 |
68 | to the "[gcm]" section of your "conf/ovms_server.conf".
69 |
70 | Verify the server can read the file, but no other users on a shared server.
71 | Check the server startup log for errors.
72 |
73 | When registering a vehicle in the App, enter your project number as noted above
74 | as the "Sender ID".
75 |
76 | If the server encounters permission errors when sending notifications, verify
77 | the Cloud project has the APIs "Firebase Cloud Messaging API" and "Firebase
78 | Installations API" enabled. Note: enabling new APIs can take some time to
79 | propagate.
80 |
81 |
82 |
--------------------------------------------------------------------------------
/v3/demos/ovms_democar.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 |
3 | use EV;
4 | use AnyEvent;
5 | use AnyEvent::Handle;
6 | use AnyEvent::Socket;
7 | use Digest::MD5;
8 | use Digest::HMAC;
9 | use Crypt::RC4::XS;
10 | use MIME::Base64;
11 | use IO::Socket::INET;
12 | use Math::Trig qw(deg2rad pi great_circle_distance asin acos);
13 |
14 | select STDOUT; $|=1;
15 |
16 | my $b64tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
17 |
18 | my $shared_secret = "DEMO";
19 | print STDERR "Shared Secret is: ",$shared_secret,"\n";
20 |
21 | #####
22 | ##### CONNECT
23 | #####
24 |
25 | my $sock = IO::Socket::INET->new(PeerAddr => '127.0.0.1',
26 | PeerPort => '6867',
27 | Proto => 'tcp');
28 |
29 | die "Connection error: $!" if (!defined $sock);
30 |
31 | #####
32 | ##### CLIENT
33 | #####
34 |
35 | my @latitudes = ( 22.270553, 22.272161, 22.301067, 22.375294, 22.286586, 22.307848, 22.262411, 22.492383,
36 | 22.338058, 22.442075, 22.288353, 22.371672, 22.324756, 22.243414, 22.323072, 22.426144,
37 | 22.274433, 22.366056, 22.310994, 22.502972, 22.319078, 22.295394, 22.337297, 22.342644,
38 | 22.320317, 22.300880, 22.295550, 22.280397, 22.315425, 22.277361, 22.369086, 22.382773,
39 | 22.365850, 22.282833, 22.450369, 22.319344, 22.270489, 22.282989, 22.449021, 22.393533,
40 | 22.274311, 22.442208, 22.270363, 22.280659, 22.278494, 22.280464, 22.286659, 22.267424,
41 | 22.266014, 22.280827, 22.287945, 22.286108, 22.280728, 22.282644, 22.273867, 22.281420,
42 | 22.278444, 22.303761, 22.311768, 22.321377, 22.313337, 22.311876, 22.309065, 22.324429,
43 | 22.325256, 22.313612, 22.313227, 22.325961, 22.313087, 22.340977, 22.340788, 22.337493,
44 | 22.302919, 22.298368, 22.297485, 22.317623, 22.319450, 22.290963, 22.361467, 22.487410,
45 | 22.506491, 22.499964, 22.500574, 22.389791, 22.397291, 22.388657, 22.424746, 22.375674,
46 | 22.363600, 22.371627, 22.372817, 22.372320, 22.383770, 22.396284, 22.393525, 22.393525,
47 | 22.445194, 22.446680, 22.440573, 22.319101, 22.373611 );
48 | my @longitudes = ( 114.186994, 114.186064, 114.167956, 114.111264, 114.218114, 114.162031, 114.130567, 114.138867,
49 | 114.173817, 114.028017, 113.941636, 113.993500, 114.173128, 114.148067, 114.204267, 114.210080,
50 | 114.171048, 114.137133, 114.225769, 114.127592, 114.168436, 114.272208, 114.187969, 114.190697,
51 | 114.208603, 114.172019, 114.169275, 114.226847, 114.162317, 114.165336, 114.120258, 114.189416,
52 | 114.140106, 114.160631, 114.160903, 114.156444, 114.149522, 114.191528, 114.002949, 113.976739,
53 | 114.172247, 114.027564, 114.149948, 114.154709, 114.160867, 114.156865, 114.191566, 114.242821,
54 | 114.250496, 114.224365, 114.202827, 114.196198, 114.174889, 114.173044, 114.187346, 114.182404,
55 | 114.181541, 114.186460, 114.189247, 114.209373, 114.219511, 114.224358, 114.221664, 114.210350,
56 | 114.210876, 114.218033, 114.219772, 114.204940, 114.219345, 114.171577, 114.202516, 114.214618,
57 | 114.169495, 114.178352, 114.173419, 114.155945, 114.158206, 113.942835, 114.123802, 114.142662,
58 | 114.122136, 114.144508, 114.143852, 114.207764, 114.193474, 114.207840, 114.228050, 114.112716,
59 | 114.114326, 113.992403, 113.991780, 113.969810, 113.971344, 113.969627, 113.976739, 113.976739,
60 | 114.031456, 114.021980, 114.021744, 113.943900, 114.109540 );
61 |
62 | print STDERR "I am the demo car\n";
63 |
64 | rand;
65 | our ($client_token, $txcipher, $rxcipher);
66 | foreach (0 .. 21)
67 | { $client_token .= substr($b64tab,rand(64),1); }
68 | print STDERR " Client token is $client_token\n";
69 |
70 | my $client_hmac = Digest::HMAC->new($shared_secret, "Digest::MD5");
71 | $client_hmac->add($client_token);
72 | my $client_digest = $client_hmac->b64digest();
73 | print STDERR " Client digest is $client_digest\n";
74 |
75 | # Now, let's go for it...
76 | my $travelling = 1;
77 | my $lat = $latitudes[0];
78 | my $lon = $longitudes[0];
79 | my ($soc,$kmideal,$kmest,$state,$mode,$volts,$amps) = (30,int((30.0/100.0)*350.0),int((30.0/100.0)*350.0)-20,'done','standard',0,0);
80 | my ($substate,$stateN,$modeN) = (7,13,0);
81 | my $trip = 0;
82 | my $odometer = 0;
83 | my $chargetime = 0;
84 | my @features = (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
85 | my @parameters = ("","","","","","","","","","","","","","","","","","","","","","","","");
86 | my ($ambiant,$nextambiant)=(0,0);
87 | my $lockunlock = 4;
88 | my $valetmode = 0;
89 | &getambiant();
90 |
91 | srand();
92 |
93 | my $handle = new AnyEvent::Handle(fh => $sock, on_error => \&io_error, keepalive => 1, no_delay => 1);
94 | $handle->push_write("MP-C 0 $client_token $client_digest DEMO\r\n");
95 | $handle->push_read (line => \&io_login);
96 |
97 | my $tim = AnyEvent->timer (after => 60, interval => 60, cb => \&io_tim);
98 |
99 | # Main event loop...
100 | EV::loop();
101 |
102 | sub io_error
103 | {
104 | my ($hdl, $fatal, $msg) = @_;
105 |
106 | undef $hdl;
107 | undef $handle;
108 | print "Got error: $msg\n";
109 | sleep 30;
110 | exec './ovms_democar.pl';
111 | }
112 |
113 | sub io_tim
114 | {
115 | my ($hdl) = @_;
116 |
117 | if ($travelling)
118 | {
119 | my $newdest = int(rand(scalar @longitudes - 1));
120 | my $newlon = $longitudes[$newdest];
121 | my $newlat = $latitudes[$newdest];
122 | my $dist = int(&Haversine($lat, $lon, $newlat, $newlon));
123 | $trip += $dist*10;
124 | $odometer += $dist*10;
125 | ($lat,$lon) = ($newlat,$newlon);
126 | $kmideal = $kmideal-$dist;
127 | $kmest = int($kmideal*0.9);
128 | $soc = int(100*$kmideal/350);
129 | print "Travelling ",$dist,"km - now SOC is $soc ($kmideal,$kmest)\n";
130 | if ($soc < 30)
131 | {
132 | ($travelling,$volts,$amps,$state,$mode,$chargetime) = (0,220,13,"charging","standard",0);
133 | ($substate,$stateN,$modeN) = (5,1,0);
134 | }
135 | }
136 | else
137 | {
138 | $chargetime++;
139 | $kmideal += 10;
140 | $kmest = int($kmideal*0.9);
141 | $soc = int(100*$kmideal/350);
142 | print "Charging - now SOC is $soc ($kmideal,$kmest)\n";
143 | if ($soc > 95)
144 | {
145 | ($travelling,$volts,$amps,$state,$mode,$chargetime) = (1,0,0,"done","standard",0);
146 | ($substate,$stateN,$modeN) = (7,13,0);
147 | }
148 | $trip = 0;
149 | }
150 |
151 | if (--$nextambiant <= 0)
152 | {
153 | &getambiant();
154 | }
155 |
156 | &statmsg();
157 | }
158 |
159 | sub io_login
160 | {
161 | my ($hdl, $line) = @_;
162 |
163 | print STDERR " Received login $line from server\n";
164 | my ($welcome,$crypt,$server_token,$server_digest) = split /\s+/,$line;
165 |
166 | print STDERR " Received $server_token $server_digest from server\n";
167 |
168 | my $d_server_digest = decode_base64($server_digest);
169 | my $client_hmac = Digest::HMAC->new($shared_secret, "Digest::MD5");
170 | $client_hmac->add($server_token);
171 | if ($client_hmac->digest() ne $d_server_digest)
172 | {
173 | print STDERR " Client detects server digest is invalid - aborting\n";
174 | exit(1);
175 | }
176 | print STDERR " Client verification of server digest is ok\n";
177 |
178 | $client_hmac = Digest::HMAC->new($shared_secret, "Digest::MD5");
179 | $client_hmac->add($server_token);
180 | $client_hmac->add($client_token);
181 | my $client_key = $client_hmac->digest;
182 | print STDERR " Client version of shared key is: ",encode_base64($client_key,''),"\n";
183 |
184 | $txcipher = Crypt::RC4::XS->new($client_key);
185 | $txcipher->RC4(chr(0) x 1024); # Prime the cipher
186 |
187 | $rxcipher = Crypt::RC4::XS->new($client_key);
188 | $rxcipher->RC4(chr(0) x 1024); # Prime the cipher
189 |
190 | $handle->push_read (line => \&io_line);
191 |
192 | &statmsg();
193 | };
194 |
195 | sub io_line
196 | {
197 | my ($hdl, $line) = @_;
198 |
199 | print " Received $line from server\n";
200 |
201 | $line = $rxcipher->RC4(decode_base64($line));
202 | print " Server message decodes to: $line\n";
203 | if ($line =~ /^MP-0 A/)
204 | {
205 | my $encrypted = encode_base64($txcipher->RC4("MP-0 a"),'');
206 | print STDERR " Sending message $encrypted\n";
207 | $hdl->push_write("$encrypted\r\n");
208 | }
209 | elsif (($line =~ /^MP-0 Z(\d+)/)&&($1>0))
210 | { # One or more apps connected...
211 | &statmsg();
212 | }
213 | elsif ($line =~ /^MP-0 C(\d+)(,(.*))?/)
214 | { # A command...
215 | &command($1,split(/,/,$3));
216 | }
217 | $hdl->push_read (line => \&io_line);
218 | }
219 |
220 | sub command
221 | {
222 | my ($cmd,@params) = @_;
223 |
224 | print " Command is $cmd, params are ",join(',',@params),"\n";
225 | if ($cmd == 1) # Request feature list
226 | {
227 | foreach (0 .. 15)
228 | {
229 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 c".$cmd.",0,".$_.",16,".$features[$_]),'')."\r\n");
230 | }
231 | }
232 | elsif ($cmd == 2) # Set feature
233 | {
234 | $features[$params[0]] = $params[1];
235 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 c".$cmd.",0,Feature Set"),'')."\r\n");
236 | }
237 | elsif ($cmd == 3) # Request parameter list
238 | {
239 | foreach (0 .. 23)
240 | {
241 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 c".$cmd.",0,".$_.",24,".$parameters[$_]),'')."\r\n");
242 | }
243 | }
244 | elsif ($cmd == 4) # Set parameter
245 | {
246 | $parameters[$params[0]] = $params[1];
247 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 c".$cmd.",0,Parameter Set"),'')."\r\n");
248 | }
249 | elsif ($cmd == 5) # Reboot module
250 | {
251 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 c".$cmd.",0,Module Rebooted"),'')."\r\n");
252 | }
253 | elsif ($cmd == 7) # Handle command
254 | {
255 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 c".$cmd.",0,Command response for: ".join(' ',@params)),'')."\r\n");
256 | }
257 | elsif ($cmd == 10) # Set charge mode
258 | {
259 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 c".$cmd.",0,Charge Parameters Set"),'')."\r\n");
260 | }
261 | elsif ($cmd == 11) # Start charge
262 | {
263 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 c".$cmd.",0,Charge Started"),'')."\r\n");
264 | ($travelling,$volts,$amps,$state,$mode,$chargetime) = (0,220,13,"charging","standard",0);
265 | ($substate,$stateN,$modeN) = (5,1,0);
266 | &io_tim($handle);
267 | &statmsg();
268 | }
269 | elsif ($cmd == 12) # Stop charge
270 | {
271 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 c".$cmd.",0,Charge Stopped"),'')."\r\n");
272 | ($travelling,$volts,$amps,$state,$mode,$chargetime) = (1,0,0,"done","standard",0);
273 | ($substate,$stateN,$modeN) = (7,13,0);
274 | &io_tim($handle);
275 | &statmsg();
276 | }
277 | elsif ($cmd == 15) # Set charge current
278 | {
279 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 c".$cmd.",0,Charge Parameters Set"),'')."\r\n");
280 | }
281 | elsif ($cmd == 16) # Set charge mode and current
282 | {
283 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 c".$cmd.",0,Charge Parameters Set"),'')."\r\n");
284 | }
285 | elsif ($cmd == 17) # Set charge timer and start charge time
286 | {
287 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 c".$cmd.",3,Command unimplemented"),'')."\r\n");
288 | }
289 | elsif ($cmd == 18) # Wakeup car
290 | {
291 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 c".$cmd.",0,Vehicle Awake"),'')."\r\n");
292 | }
293 | elsif ($cmd == 19) # Wakeup temp subsystems
294 | {
295 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 c".$cmd.",0,Vehicle Awake"),'')."\r\n");
296 | }
297 | elsif ($cmd == 20) # Lock car
298 | {
299 | $lockunlock = 4;
300 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 c".$cmd.",0,Vehicle Locked"),'')."\r\n");
301 | &statmsg();
302 | }
303 | elsif ($cmd == 21) # Activate valet mode
304 | {
305 | $valetmode = 1;
306 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 c".$cmd.",0,Valet Mode Activated"),'')."\r\n");
307 | &statmsg();
308 | }
309 | elsif ($cmd == 22) # Unlock car
310 | {
311 | $lockunlock = 5;
312 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 c".$cmd.",0,Vehicle Unlocked"),'')."\r\n");
313 | &statmsg();
314 | }
315 | elsif ($cmd == 23) # Deactivate valet mode
316 | {
317 | $valetmode = 0;
318 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 c".$cmd.",0,Valet Mode Deactivated"),'')."\r\n");
319 | &statmsg();
320 | }
321 | elsif ($cmd == 24) # Home link
322 | {
323 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 c".$cmd.",0,Home Link requested"),'')."\r\n");
324 | }
325 | elsif ($cmd == 40) # Send SMS
326 | {
327 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 c".$cmd.",3,Command unimplemented"),'')."\r\n");
328 | }
329 | elsif ($cmd == 41) # Send USSD/MMI Codes
330 | {
331 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 c".$cmd.",3,Command unimplemented"),'')."\r\n");
332 | }
333 | elsif ($cmd == 49) # Send raw AT command
334 | {
335 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 c".$cmd.",3,Command unimplemented"),'')."\r\n");
336 | }
337 | else
338 | {
339 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 c".$cmd.",3,Command unimplemented"),'')."\r\n");
340 | }
341 | }
342 |
343 | sub statmsg
344 | {
345 | my ($front,$back) = ($ambiant+2,$ambiant+6);
346 | my ($pem,$motor,$battery) = ($ambiant+10,$ambiant+20,$ambiant+5);
347 | my $cb = ($travelling == 0)?124:160;
348 | my $speed = ($travelling == 0)?0:55;
349 | my $doors2 = ($valetmode == 0)?0:16;
350 | $doors2 += 8 if ($lockunlock==4);
351 |
352 | print STDERR " Sending status...\n";
353 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 F1.5.0,VIN123456789012345,5,1,TRDM,"),'')."\r\n");
354 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 S$soc,K,$volts,$amps,$state,$mode,$kmideal,$kmest,13,$chargetime,0,0,$substate,$stateN,$modeN,0,0,1"),'')."\r\n");
355 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 L$lat,$lon,90,0,1,1"),'')."\r\n");
356 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 D$cb,$doors2,$lockunlock,$pem,$motor,$battery,$trip,$odometer,50,0,$ambiant,0,1,1,12.0,0"),"")."\r\n");
357 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 W29,$front,40,$back,29,$front,40,$back,1"),"")."\r\n");
358 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 gDEMOCARS,$soc,$speed,90,0,1,120,$lat,$lon"),'')."\r\n");
359 | # $handle->push_write(encode_base64($txcipher->RC4("MP-0 PETR,104,16384"),'')."\r\n");
360 | # $handle->push_write(encode_base64($txcipher->RC4("MP-0 PIHello demo world"),'')."\r\n");
361 | }
362 |
363 | sub getambiant
364 | {
365 | print STDERR " Getting ambiant temperature for Hong Kong\n";
366 | my $weather = `curl http://rss.weather.gov.hk/rss/CurrentWeather.xml 2>/dev/null`;
367 |
368 | if ($weather =~ /Air temperature.+\s(\d+)\s/)
369 | {
370 | $ambiant = $1;
371 | print " Ambiant is $ambiant celcius\n";
372 | }
373 | $nextambiant = 60;
374 | }
375 |
376 | sub Haversine
377 | {
378 | my ($lat1, $long1, $lat2, $long2) = @_;
379 | my $r=3956;
380 |
381 | $dlong = deg2rad($long1) - deg2rad($long2);
382 | $dlat = deg2rad($lat1) - deg2rad($lat2);
383 |
384 | $a = sin($dlat/2)**2 +cos(deg2rad($lat1))
385 | * cos(deg2rad($lat2))
386 | * sin($dlong/2)**2;
387 | $c = 2 * (asin(sqrt($a)));
388 | $dist = $r * $c;
389 |
390 | return $dist*1.609344;
391 | }
392 |
--------------------------------------------------------------------------------
/v3/demos/ovms_rally.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 |
3 | use EV;
4 | use AnyEvent;
5 | use AnyEvent::Handle;
6 | use AnyEvent::Socket;
7 | use Digest::MD5;
8 | use Digest::HMAC;
9 | use Crypt::RC4::XS;
10 | use MIME::Base64;
11 | use IO::Socket::INET;
12 | use Math::Trig qw(deg2rad pi great_circle_distance asin acos);
13 |
14 | my $b64tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
15 |
16 | my $shared_secret = "RALLY";
17 | print STDERR "Shared Secret is: ",$shared_secret,"\n";
18 |
19 | #####
20 | ##### CONNECT
21 | #####
22 |
23 | my $sock = IO::Socket::INET->new(PeerAddr => '127.0.0.1',
24 | PeerPort => '6867',
25 | Proto => 'tcp');
26 |
27 | #####
28 | ##### CLIENT
29 | #####
30 |
31 | print STDERR "I am the rally car\n";
32 |
33 | rand;
34 | my $client_token;
35 | foreach (0 .. 21)
36 | { $client_token .= substr($b64tab,rand(64),1); }
37 | print STDERR " Client token is $client_token\n";
38 |
39 | my $client_hmac = Digest::HMAC->new($shared_secret, "Digest::MD5");
40 | $client_hmac->add($client_token);
41 | my $client_digest = $client_hmac->b64digest();
42 | print STDERR " Client digest is $client_digest\n";
43 |
44 | print $sock "MP-C 0 $client_token $client_digest RALLY\r\n";
45 |
46 | my $line = <$sock>;
47 | chop $line;
48 | chop $line;
49 | print STDERR " Received $line from server\n";
50 | my ($welcome,$crypt,$server_token,$server_digest) = split /\s+/,$line;
51 |
52 | print STDERR " Received $server_token $server_digest from server\n";
53 |
54 | my $d_server_digest = decode_base64($server_digest);
55 | my $client_hmac = Digest::HMAC->new($shared_secret, "Digest::MD5");
56 | $client_hmac->add($server_token);
57 | if ($client_hmac->digest() ne $d_server_digest)
58 | {
59 | print STDERR " Client detects server digest is invalid - aborting\n";
60 | exit(1);
61 | }
62 | print STDERR " Client verification of server digest is ok\n";
63 |
64 | $client_hmac = Digest::HMAC->new($shared_secret, "Digest::MD5");
65 | $client_hmac->add($server_token);
66 | $client_hmac->add($client_token);
67 | my $client_key = $client_hmac->digest;
68 | print STDERR " Client version of shared key is: ",encode_base64($client_key,''),"\n";
69 |
70 | our $txcipher = Crypt::RC4::XS->new($client_key);
71 | $txcipher->RC4(chr(0) x 1024); # Prime the cipher
72 | our $rxcipher = Crypt::RC4::XS->new($client_key);
73 | $rxcipher->RC4(chr(0) x 1024); # Prime the cipher
74 |
75 | my $encrypted = encode_base64($txcipher->RC4("MP-0 A"),'');
76 | print STDERR " Sending message $encrypted\n";
77 | print $sock "$encrypted\r\n";
78 |
79 | $line = <$sock>;
80 | chop $line;
81 | chop $line;
82 | print STDERR " Received $line from server\n";
83 | print STDERR " Server message decodes to: ",$rxcipher->RC4(decode_base64($line)),"\n";
84 |
85 | # Now, let's go for it...
86 | my $rally_data; open $rally_data,"; chop $rally_line;
88 | my ($lat,$lon,$bearing,$dist,$speed) = split /\s+/,$rally_line;
89 |
90 | my ($soc,$kmideal,$kmest,$state,$mode,$volts,$amps) = (87,int((87.0/100.0)*350.0),int((87.0/100.0)*350.0)-20,'done','standard',0,0);
91 | my ($substate,$stateN,$modeN) = (7,13,0);
92 | my $trip = $dist;
93 | my $odometer = $dist;
94 | my $chargetime = 0;
95 | my $appsconnected = 0;
96 | my $lastupdated = 0;
97 |
98 | my ($ambiant,$nextambiant)=(0,0);
99 | &getambiant();
100 |
101 | srand();
102 |
103 | my $handle = new AnyEvent::Handle(fh => $sock, on_error => \&io_error, keepalive => 1, no_delay => 1);
104 | $handle->push_read (line => \&io_line);
105 | &statmsg();
106 |
107 | my $tim = AnyEvent->timer (after => 2, interval => 2, cb => \&io_tim);
108 |
109 | # Main event loop...
110 | EV::loop();
111 |
112 | sub io_error
113 | {
114 | my ($hdl, $fatal, $msg) = @_;
115 |
116 | undef $hdl;
117 | undef $handle;
118 | print "Got error: $msg\n";
119 | sleep 30;
120 | exec './ovms_rally.pl';
121 | }
122 |
123 | sub io_tim
124 | {
125 | my ($hdl) = @_;
126 |
127 | my $rally_line = <$rally_data>;
128 | if (!defined $rally_line)
129 | {
130 | close $rally_data;
131 | open $rally_data,"RC4(decode_base64($line));
162 | print " Server message decodes to: $line\n";
163 | if ($line =~ /^MP-0 A/)
164 | {
165 | my $encrypted = encode_base64($txcipher->RC4("MP-0 a"),'');
166 | print STDERR " Sending message $encrypted\n";
167 | print $hdl->push_write("$encrypted\r\n");
168 | }
169 | elsif ($line =~ /^MP-0 Z(\d+)/)
170 | { # One or more apps connected...
171 | $appsconnected = $1;
172 | if ($appsconnected>0)
173 | {
174 | $lastupdate = 0;
175 | &statmsg();
176 | }
177 | }
178 | elsif ($line =~ /^MP-0 C(\d+)/)
179 | { # A command...
180 | my $encrypted = encode_base64($txcipher->RC4("MP-0 c$1,1,Comand refused"),'');
181 | print STDERR " Sending message $encrypted\n";
182 | print $hdl->push_write("$encrypted\r\n");
183 | &statmsg();
184 | }
185 | $hdl->push_read (line => \&io_line);
186 | }
187 |
188 | sub statmsg
189 | {
190 | my ($front,$back) = ($ambiant+2,$ambiant+6);
191 | my ($pem,$motor,$battery) = ($ambiant+10,$ambiant+20,$ambiant+5);
192 | my $cb = ($travelling == 0)?124:160;
193 | my ($ikmideal,$ikmest) = (int($kmideal),int($kmest));
194 |
195 | my $updates=0;
196 | $updates+= &sendmessage("F","1.2.0,VIN123456789012346,5,0,TR");
197 | $updates+= &sendmessage("S","$soc,K,$volts,$amps,$state,$mode,$ikmideal,$ikmest,13,$chargetime,0,0,$substate,$stateN,$modeN");
198 | $updates+= &sendmessage("L","$lat,$lon,$bearing,0,1,1");
199 | $updates+= &sendmessage("D","$cb,0,5,$pem,$motor,$battery,$trip,$odometer,$speed,0,$ambiant,0,1,1");
200 | $updates+= &sendmessage("W","29,$front,40,$back,29,$front,40,$back,1");
201 | $updates+= &sendmessage("g","DEMOCARS,$soc,$speed,$bearing,0,1,120,$lat,$lon");
202 |
203 | $lastupdated = time if ($updates>0);
204 | }
205 |
206 | my %prevmessages;
207 | sub sendmessage
208 | {
209 | my ($code,$data) = @_;
210 |
211 | my $updates = 0;
212 |
213 | my $forceupdate = (($lastupdated==0)||((time-$lastupdated)>=600))?1:0;
214 |
215 | if (($forceupdate)||($appsconnected > 0))
216 | {
217 | if ((!defined $prevmessages{$code})||($prevmessages{$code} ne $data)||$forceupdate)
218 | {
219 | print STDERR " Sending $code $data\n";
220 | $handle->push_write(encode_base64($txcipher->RC4("MP-0 $code$data"),'')."\r\n");
221 | $prevmessages{$code} = $data;
222 | $updates++;
223 | }
224 | }
225 |
226 | return $updates;
227 | }
228 |
229 | sub getambiant
230 | {
231 | print STDERR " Getting ambiant temperature for Hong Kong\n";
232 | my $weather = `curl http://rss.weather.gov.hk/rss/CurrentWeather.xml 2>/dev/null`;
233 |
234 | if ($weather =~ /Air temperature.+\s(\d+)\s/)
235 | {
236 | $ambiant = $1;
237 | print " Ambiant is $ambiant celcius\n";
238 | }
239 | $nextambiant = 1800;
240 | }
241 |
242 |
--------------------------------------------------------------------------------
/v3/server/SETUP:
--------------------------------------------------------------------------------
1 | HOWTO Setup an OVMS server
2 | ==========================
3 |
4 | 0. DO I NEED THIS?
5 |
6 | No, you don't need to run your own OVMS server.
7 | You can just use a public Open Vehicles OVMS server.
8 | The choice is yours.
9 |
10 |
11 | 1. HOW TO INSTALL AN OVMS SERVER
12 |
13 | See: https://docs.openvehicles.com/en/latest/server/installation.html
14 |
--------------------------------------------------------------------------------
/v3/server/conf/ovms_server.conf.default:
--------------------------------------------------------------------------------
1 | [db]
2 | path=DBI:mysql:database=openvehicles;host=127.0.0.1
3 | user=
4 | pass=
5 | pw_encode=drupal_password($password)
6 |
7 | # example: salting with secret salt & sha256 hashing:
8 | #pw_encode=unpack("H*", sha256($password . "my_secret_salt_string"))
9 |
10 | [log]
11 | level=info
12 | history=86400
13 |
14 | [push]
15 | history=2592000
16 |
17 | [server]
18 | timeout_app=1200
19 | timeout_car=960
20 | timeout_svr=3600
21 | timeout_api=300
22 |
23 | [mail]
24 | enabled=0
25 | interval=10
26 | sender=notifications@openvehicles.com
27 |
28 | [gcm]
29 | apikey=
30 |
31 | [plugins]
32 | load=<level (MyConfig()->val('log','level','info'));
35 |
36 | my $info_tim = AnyEvent->timer (after => 10, interval => 10, cb => \&info_tim);
37 |
38 | RegisterFunction('InfoCount',\&info_count);
39 | my $plugin_mgr = new OVMS::Server::Plugin();
40 |
41 | # Auto-flush
42 | select STDERR; $|=1;
43 | select STDOUT; $|=1;
44 |
45 | ########################################################
46 | # Main event loop entry
47 |
48 | EventCall('StartRun');
49 |
50 | EV::loop();
51 |
52 | ########################################################
53 | # Information timer
54 |
55 | my %info_counts;
56 | sub info_count
57 | {
58 | my ($topic,$count) = @_;
59 |
60 | if ($count > 0)
61 | { $info_counts{$topic} = $count; }
62 | else
63 | { delete $info_counts{$topic} }
64 | }
65 |
66 | sub info_tim
67 | {
68 | # Log current informational counts
69 |
70 | my @counts;
71 | foreach my $topic (sort keys %info_counts)
72 | {
73 | push @counts,"$topic=".$info_counts{$topic};
74 | }
75 |
76 | return if (scalar @counts == 0);
77 |
78 | AE::log info => "- - - statistics: " . join(', ',@counts);
79 | }
80 |
--------------------------------------------------------------------------------
/v3/server/ovms_server.sql:
--------------------------------------------------------------------------------
1 | -- MySQL dump 10.13 Distrib 5.7.28, for Linux (x86_64)
2 | --
3 | -- Host: localhost Database: openvehicles
4 | -- ------------------------------------------------------
5 | -- Server version 5.7.28
6 |
7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
9 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
10 | /*!40101 SET NAMES utf8 */;
11 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
12 | /*!40103 SET TIME_ZONE='+00:00' */;
13 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
15 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
16 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
17 |
18 | --
19 | -- Table structure for table `ovms_apitokens`
20 | --
21 |
22 | DROP TABLE IF EXISTS `ovms_apitokens`;
23 | /*!40101 SET @saved_cs_client = @@character_set_client */;
24 | /*!40101 SET character_set_client = utf8 */;
25 | CREATE TABLE `ovms_apitokens` (
26 | `owner` int(10) unsigned NOT NULL,
27 | `token` varchar(64) NOT NULL,
28 | `application` varchar(32) NOT NULL DEFAULT '',
29 | `purpose` varchar(80) NOT NULL DEFAULT '',
30 | `permit` varchar(255) NOT NULL DEFAULT 'none',
31 | `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
32 | `refreshed` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
33 | `lastused` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
34 | PRIMARY KEY (`owner`,`token`),
35 | KEY `token` (`token`)
36 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='OVMS: API tokens';
37 | /*!40101 SET character_set_client = @saved_cs_client */;
38 |
39 | --
40 | -- Table structure for table `ovms_autoprovision`
41 | --
42 |
43 | DROP TABLE IF EXISTS `ovms_autoprovision`;
44 | /*!40101 SET @saved_cs_client = @@character_set_client */;
45 | /*!40101 SET character_set_client = utf8 */;
46 | CREATE TABLE `ovms_autoprovision` (
47 | `ap_key` varchar(64) NOT NULL DEFAULT '' COMMENT 'Unique Auto-Provisioning Key',
48 | `ap_stoken` varchar(32) NOT NULL DEFAULT '',
49 | `ap_sdigest` varchar(32) NOT NULL DEFAULT '',
50 | `ap_msg` varchar(4096) NOT NULL DEFAULT '',
51 | `deleted` tinyint(1) NOT NULL DEFAULT '0',
52 | `changed` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
53 | `owner` int(10) unsigned NOT NULL DEFAULT '0',
54 | `v_lastused` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
55 | PRIMARY KEY (`ap_key`)
56 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='OVMS: Auto-Provisioning Records';
57 | /*!40101 SET character_set_client = @saved_cs_client */;
58 |
59 | --
60 | -- Table structure for table `ovms_carmessages`
61 | --
62 |
63 | DROP TABLE IF EXISTS `ovms_carmessages`;
64 | /*!40101 SET @saved_cs_client = @@character_set_client */;
65 | /*!40101 SET character_set_client = utf8 */;
66 | CREATE TABLE `ovms_carmessages` (
67 | `owner` int(10) unsigned NOT NULL DEFAULT '0',
68 | `vehicleid` varchar(32) NOT NULL DEFAULT '' COMMENT 'Unique vehicle ID',
69 | `m_code` char(1) NOT NULL DEFAULT '',
70 | `m_valid` tinyint(1) NOT NULL DEFAULT '1',
71 | `m_msgtime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
72 | `m_paranoid` tinyint(1) NOT NULL DEFAULT '0',
73 | `m_ptoken` varchar(32) NOT NULL DEFAULT '',
74 | `m_msg` varchar(255) NOT NULL DEFAULT '',
75 | PRIMARY KEY (`owner`,`vehicleid`,`m_code`)
76 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='OVMS: Stores vehicle messages';
77 | /*!40101 SET character_set_client = @saved_cs_client */;
78 |
79 | --
80 | -- Table structure for table `ovms_cars`
81 | --
82 |
83 | DROP TABLE IF EXISTS `ovms_cars`;
84 | /*!40101 SET @saved_cs_client = @@character_set_client */;
85 | /*!40101 SET character_set_client = utf8 */;
86 | CREATE TABLE `ovms_cars` (
87 | `vehicleid` varchar(32) NOT NULL DEFAULT '' COMMENT 'Unique vehicle ID',
88 | `vehiclename` varchar(64) NOT NULL DEFAULT '',
89 | `owner` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'Owner user ID.',
90 | `telephone` varchar(48) NOT NULL DEFAULT '',
91 | `carpass` varchar(255) NOT NULL DEFAULT '' COMMENT 'Car password',
92 | `userpass` varchar(255) NOT NULL DEFAULT '' COMMENT 'User password (optional)',
93 | `cryptscheme` varchar(1) NOT NULL DEFAULT '0',
94 | `v_ptoken` varchar(32) NOT NULL DEFAULT '',
95 | `v_server` varchar(32) NOT NULL DEFAULT '*',
96 | `v_type` varchar(10) NOT NULL DEFAULT 'CAR',
97 | `deleted` tinyint(1) NOT NULL DEFAULT '0',
98 | `changed` datetime NOT NULL DEFAULT '1900-01-01 00:00:00',
99 | `v_lastupdate` datetime NOT NULL DEFAULT '1900-01-01 00:00:00',
100 | `couponcode` varchar(32) NOT NULL DEFAULT '',
101 | PRIMARY KEY (`owner`,`vehicleid`),
102 | KEY `owner` (`owner`)
103 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='OVMS: Stores vehicle current data';
104 | /*!40101 SET character_set_client = @saved_cs_client */;
105 |
106 | --
107 | -- Table structure for table `ovms_historicalmessages`
108 | --
109 |
110 | DROP TABLE IF EXISTS `ovms_historicalmessages`;
111 | /*!40101 SET @saved_cs_client = @@character_set_client */;
112 | /*!40101 SET character_set_client = utf8 */;
113 | CREATE TABLE `ovms_historicalmessages` (
114 | `owner` int(10) unsigned NOT NULL DEFAULT '0',
115 | `vehicleid` varchar(32) NOT NULL DEFAULT '' COMMENT 'Unique vehicle ID',
116 | `h_timestamp` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
117 | `h_recordtype` varchar(32) NOT NULL DEFAULT '',
118 | `h_recordnumber` int(5) NOT NULL DEFAULT '0',
119 | `h_data` text NOT NULL,
120 | `h_expires` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
121 | PRIMARY KEY (`owner`,`vehicleid`,`h_recordtype`,`h_recordnumber`,`h_timestamp`),
122 | KEY `h_expires` (`h_expires`),
123 | KEY `h_recordtype` (`h_recordtype`)
124 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='OVMS: Stores historical data records';
125 | /*!40101 SET character_set_client = @saved_cs_client */;
126 |
127 | --
128 | -- Table structure for table `ovms_notifies`
129 | --
130 |
131 | DROP TABLE IF EXISTS `ovms_notifies`;
132 | /*!40101 SET @saved_cs_client = @@character_set_client */;
133 | /*!40101 SET character_set_client = utf8 */;
134 | CREATE TABLE `ovms_notifies` (
135 | `owner` int(10) unsigned NOT NULL DEFAULT '0',
136 | `vehicleid` varchar(32) NOT NULL DEFAULT '' COMMENT 'Unique vehicle ID',
137 | `appid` varchar(128) NOT NULL DEFAULT '' COMMENT 'Unique App ID',
138 | `pushtype` varchar(16) NOT NULL DEFAULT '',
139 | `pushkeytype` varchar(16) NOT NULL DEFAULT '',
140 | `pushkeyvalue` varchar(256) NOT NULL DEFAULT '',
141 | `lastupdated` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
142 | `active` tinyint(1) NOT NULL DEFAULT '1',
143 | PRIMARY KEY (`owner`,`vehicleid`,`appid`)
144 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='OVMS: Stores app notification configs';
145 | /*!40101 SET character_set_client = @saved_cs_client */;
146 |
147 | --
148 | -- Table structure for table `ovms_owners`
149 | --
150 |
151 | DROP TABLE IF EXISTS `ovms_owners`;
152 | /*!40101 SET @saved_cs_client = @@character_set_client */;
153 | /*!40101 SET character_set_client = utf8 */;
154 | CREATE TABLE `ovms_owners` (
155 | `owner` int(10) unsigned NOT NULL,
156 | `name` varchar(60) NOT NULL DEFAULT '',
157 | `mail` varchar(254) NOT NULL DEFAULT '',
158 | `pass` varchar(128) NOT NULL DEFAULT '',
159 | `status` tinyint(4) NOT NULL DEFAULT '0',
160 | `deleted` tinyint(1) NOT NULL DEFAULT '0',
161 | `changed` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
162 | PRIMARY KEY (`owner`),
163 | KEY `name` (`name`)
164 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='OVMS: Stores vehicle owners';
165 | /*!40101 SET character_set_client = @saved_cs_client */;
166 | /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
167 |
168 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
169 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
170 | /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
171 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
172 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
173 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
174 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
175 |
176 | -- Dump completed on 2020-03-11 14:08:21
177 |
--------------------------------------------------------------------------------
/v3/server/ovms_server_v2_to_v3.sql:
--------------------------------------------------------------------------------
1 | -- OVMS Server Database Schema
2 | --
3 | -- V2 to V3 upgrade script
4 |
5 | --
6 | -- New ovms_apitokens table
7 | --
8 |
9 | DROP TABLE IF EXISTS `ovms_apitokens`;
10 | SET @saved_cs_client = @@character_set_client;
11 | SET character_set_client = utf8;
12 | CREATE TABLE `ovms_apitokens` (
13 | `owner` int(10) unsigned NOT NULL,
14 | `token` varchar(64) NOT NULL,
15 | `application` varchar(32) NOT NULL DEFAULT '',
16 | `purpose` varchar(80) NOT NULL DEFAULT '',
17 | `permit` varchar(255) NOT NULL DEFAULT 'none',
18 | `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
19 | `refreshed` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
20 | `lastused` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
21 | PRIMARY KEY (`owner`,`token`),
22 | KEY `token` (`token`)
23 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='OVMS: API tokens';
24 | SET character_set_client = @saved_cs_client;
25 |
26 | --
27 | -- Clean up before continuing
28 | --
29 |
30 | DELETE FROM ovms_owners WHERE deleted=1;
31 | DELETE FROM ovms_cars WHERE owner NOT IN (SELECT owner FROM ovms_owners);
32 | DELETE FROM ovms_cars WHERE deleted=1;
33 |
34 | --
35 | -- Add 'owner' to ovms_cars, alongside 'vehicleid' to make vehicle IDs unqiue per owner (not globally)
36 | --
37 |
38 | ALTER TABLE ovms_cars DROP primary key, ADD primary key (`owner`,`vehicleid`);
39 |
40 | --
41 | -- Add 'owner' to ovms_carmessages, alongside 'vehicleid' to make vehicle IDs unqiue per owner (not globally)
42 | --
43 |
44 | ALTER TABLE ovms_carmessages ADD COLUMN `owner` int(10) unsigned NOT NULL default '0' FIRST,
45 | DROP PRIMARY KEY,
46 | ADD PRIMARY KEY (`owner`,`vehicleid`,`m_code`);
47 |
48 | DELETE FROM ovms_carmessages WHERE vehicleid NOT IN (SELECT vehicleid FROM ovms_cars);
49 |
50 | UPDATE ovms_carmessages
51 | SET owner=(SELECT owner FROM ovms_cars WHERE ovms_cars.vehicleid=ovms_carmessages.vehicleid);
52 |
53 | --
54 | -- Add 'owner' to ovms_historicalmessages, alongside 'vehicleid' to make vehicle IDs unqiue per owner (not globally)
55 | --
56 |
57 | ALTER TABLE ovms_historicalmessages ADD COLUMN `owner` int(10) unsigned NOT NULL default '0' FIRST,
58 | DROP PRIMARY KEY,
59 | ADD PRIMARY KEY (`owner`,`vehicleid`,`h_recordtype`,`h_recordnumber`,`h_timestamp`);
60 |
61 | DELETE FROM ovms_historicalmessages WHERE vehicleid NOT IN (SELECT vehicleid FROM ovms_cars);
62 |
63 | UPDATE ovms_historicalmessages
64 | SET owner=(SELECT owner FROM ovms_cars WHERE ovms_cars.vehicleid=ovms_historicalmessages.vehicleid);
65 |
66 | --
67 | -- Add 'owner' to ovms_notifies, alongside 'vehicleid' to make vehicle IDs unqiue per owner (not globally)
68 | --
69 |
70 | ALTER TABLE ovms_notifies ADD COLUMN `owner` int(10) unsigned NOT NULL default '0' FIRST,
71 | DROP PRIMARY KEY,
72 | ADD PRIMARY KEY (`owner`,`vehicleid`,`appid`);
73 |
74 | DELETE FROM ovms_notifies WHERE vehicleid NOT IN (SELECT vehicleid FROM ovms_cars);
75 |
76 | UPDATE ovms_notifies
77 | SET owner=(SELECT owner FROM ovms_cars WHERE ovms_cars.vehicleid=ovms_notifies.vehicleid);
78 |
79 | --
80 | -- All done
81 | --
82 |
--------------------------------------------------------------------------------
/v3/server/plugins/obsolete/OVMS/Server/ApiHttpGroup.pm:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 |
3 | ########################################################################
4 | # HTTP API GROUP functions plugin
5 | #
6 | # This plugin provides the core HTTP API methods for GROUP support.
7 | # It requires the ApiHttp plugin to be previously loaded.
8 |
9 | package OVMS::Server::ApiHttpGroup;
10 |
11 | use strict;
12 | use warnings;
13 | use Carp;
14 |
15 | use AnyEvent;
16 | use AnyEvent::Log;
17 | use OVMS::Server::Plugin;
18 |
19 | use Exporter qw(import);
20 |
21 | our @EXPORT = qw();
22 |
23 | # API: HTTP
24 |
25 | my $me; # Reference to our singleton object
26 |
27 | if (!PluginLoaded('ApiHttp'))
28 | {
29 | AE::log error => "Error: ApiHttp MUST be loaded before this plugin";
30 | }
31 |
32 | use vars qw{
33 | };
34 |
35 | sub new
36 | {
37 | my $class = shift;
38 | $class = ref($class) || $class;
39 | my $self = {@_};
40 | bless( $self, $class );
41 |
42 | $me = $self;
43 | $self->init();
44 |
45 | FunctionCall('HttpServerRegisterCallback','/group', \&http_request_in_group);
46 |
47 | return $self;
48 | }
49 |
50 | sub init
51 | {
52 | my ($self) = @_;
53 | }
54 |
55 | ########################################################
56 | # API HTTP server plugin methods
57 |
58 | sub http_request_in_group
59 | {
60 | my ($httpd, $req) = @_;
61 |
62 | AE::log info => join(' ','http','-','-',$req->client_host.':'.$req->client_port,'-',$req->method,$req->url);
63 |
64 | my $id = $req->parm('id');
65 |
66 | my @result;
67 |
68 | push @result,<<"EOT";
69 |
70 |
71 |
72 | Open Vehicles KML
73 |
80 | EOT
81 |
82 | if (defined $group_msgs{$id})
83 | {
84 | foreach (sort keys %{$group_msgs{$id}})
85 | {
86 | my ($vehicleid,$groupmsg) = ($_,$group_msgs{$id}{$_});
87 | my ($soc,$speed,$direction,$altitude,$gpslock,$stalegps,$latitude,$longitude) = split(/,/,$groupmsg);
88 |
89 | push @result,<<"EOT";
90 |
91 | $vehicleid
92 | $vehicleid
93 | #icon
94 |
95 | $longitude,$latitude
96 |
97 |
98 | EOT
99 | }
100 | }
101 |
102 | push @result,<<"EOT";
103 |
104 |
105 | EOT
106 |
107 | $req->respond([
108 | 200, 'OK', {
109 | 'Content-Type' => 'Content-Type: application/vnd.google-earth.kml+xml'
110 | },
111 | join("\n",@result)
112 | ]);
113 | $httpd->stop_request;
114 | }
115 |
116 | 1;
117 |
--------------------------------------------------------------------------------
/v3/server/plugins/obsolete/OVMS/Server/AuthConfig.pm:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 |
3 | ########################################################################
4 | # Authentication via Configuration file
5 | #
6 | # This plugin provides for authentication via configuration file.
7 | # Note: Only one Auth* plugin should be loaded at any one time.
8 | #
9 | # This is a work-in-progress and has not been completed yet.
10 |
11 | package OVMS::Server::AuthConfig;
12 |
13 | use strict;
14 | use warnings;
15 | use Carp;
16 |
17 | use AnyEvent;
18 | use AnyEvent::Log;
19 | use Config::IniFiles;
20 | use OVMS::Server::Core;
21 | use OVMS::Server::Plugin;
22 |
23 | use Exporter qw(import);
24 |
25 | our @EXPORT = qw();
26 |
27 | # Authentication: Config (authenticate against manual entries in the configuration)
28 |
29 | my $me; # Reference to our singleton object
30 |
31 | use vars qw{
32 | $config
33 | };
34 |
35 | sub new
36 | {
37 | my $class = shift;
38 | $class = ref($class) || $class;
39 | my $self = {@_};
40 | bless( $self, $class );
41 |
42 | $me = $self;
43 | $self->init();
44 |
45 | $config = MyConfig;
46 |
47 | RegisterFunction('Authenticate',\&Authenticate);
48 |
49 | return $self;
50 | }
51 |
52 | sub init
53 | {
54 | my ($self) = @_;
55 | }
56 |
57 | sub Authenticate
58 | {
59 | my ($user,$password) = @_;
60 |
61 | if ($config->exists('plugin_auth',$user))
62 | {
63 | return '*' if ($config->val('plugin_auth',$user) eq $password);
64 | }
65 |
66 | return ''; # Authentication default
67 | }
68 |
69 | 1;
70 |
--------------------------------------------------------------------------------
/v3/server/plugins/obsolete/OVMS/Server/Cluster.pm:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 | # OVMS Server Cluster
3 |
4 | package OVMS::Server::Cluster;
5 |
6 | use strict;
7 | use warnings;
8 | use Carp;
9 |
10 | use AnyEvent;
11 | use AnyEvent::Log;
12 | use AnyEvent::Handle;
13 |
14 | use Exporter qw(import);
15 |
16 | our @EXPORT = qw();
17 |
18 | my $me; # Reference to our singleton object
19 |
20 | my %svr_conns;
21 | my $timeout_svr = MyConfig()->val('server','timeout_svr',60*60);
22 |
23 | # Server PUSH tickers
24 | my $svrtim = AnyEvent->timer (after => 30, interval => 30, cb => \&svr_tim);
25 | my $svrtim2 = AnyEvent->timer (after => 300, interval => 300, cb => \&svr_tim2);
26 |
27 | # A server client
28 | my $svr_handle;
29 | my $svr_client_token;
30 | my $svr_client_digest;
31 | my $svr_txcipher;
32 | my $svr_rxcipher;
33 | my $svr_server = MyConfig()->val('master','server');
34 | my $svr_port = MyConfig()->val('master','port',6867);
35 | my $svr_vehicle = MyConfig()->val('master','vehicle');
36 | my $svr_pass = MyConfig()->val('master','password');
37 | if (defined $svr_server)
38 | {
39 | &svr_client();
40 | }
41 |
42 | on_timeout
43 | elsif ($clienttype eq 'S')
44 | {
45 | if (($lastrx+$timeout_svr)<$now)
46 | {
47 | # The SVR has been unresponsive for timeout_svr seconds - time to disconnect it
48 | &io_terminate($fn,$hdl,$vid, "timeout svr due to inactivity");
49 | return;
50 | }
51 | }
52 |
53 | io_login
54 | elsif ($clienttype eq 'S')
55 | {
56 | #
57 | # A SERVER login
58 | #
59 | if (defined $svr_conns{$vehicleid})
60 | {
61 | # Server is already logged in - terminate it
62 | &io_terminate($svr_conns{$vehicleid},$conns{$svr_conns{$vehicleid}}{'handle'},$vehicleid, "error - duplicate server login - clearing first connection");
63 | }
64 | $svr_conns{$vehicleid} = $fn;
65 | my ($svrupdate_v,$svrupdate_o) = ($1,$2) if ($rest =~ /^(\S+ \S+) (\S+ \S+)/);
66 | $conns{$fn}{'svrupdate_v'} = $svrupdate_v;
67 | $conns{$fn}{'svrupdate_o'} = $svrupdate_o;
68 | &svr_push($fn,$vehicleid);
69 | }
70 |
71 | io_terminate
72 | elsif ($conns{$fn}{'clienttype'} eq 'S')
73 | {
74 | delete $svr_conns{$vehicleid};
75 | }
76 |
77 | use vars qw{
78 | };
79 |
80 | sub new
81 | {
82 | my $class = shift;
83 | $class = ref($class) || $class;
84 | my $self = {@_};
85 | bless( $self, $class );
86 |
87 | $me = $self;
88 | $self->init();
89 |
90 | return $self;
91 | }
92 |
93 | sub init
94 | {
95 | my ($self) = @_;
96 | }
97 |
98 |
99 | sub svr_tim
100 | {
101 | return if (scalar keys %svr_conns == 0);
102 |
103 | # Drupal -> ovms_owners maintenance
104 | $db->do('INSERT INTO ovms_owners SELECT uid,name,mail,pass,status,0,utc_timestamp() FROM users WHERE users.uid NOT IN (SELECT owner FROM ovms_owners)');
105 | $db->do('UPDATE ovms_owners LEFT JOIN users ON users.uid=ovms_owners.owner '
106 | . 'SET ovms_owners.pass=users.pass, ovms_owners.status=users.status, ovms_owners.name=users.name, ovms_owners.mail=users.mail, deleted=0, changed=UTC_TIMESTAMP() '
107 | . 'WHERE users.pass<>ovms_owners.pass OR users.status<>ovms_owners.status OR users.name<>ovms_owners.name OR users.mail<>ovms_owners.mail');
108 | $db->do('UPDATE ovms_owners SET deleted=1,changed=UTC_TIMESTAMP() WHERE deleted=0 AND owner NOT IN (SELECT uid FROM users)');
109 |
110 | my %last;
111 | my $sth = $db->prepare('SELECT v_server,MAX(changed) AS lu FROM ovms_cars WHERE v_type="CAR" GROUP BY v_server');
112 | $sth->execute();
113 | while (my $row = $sth->fetchrow_hashref())
114 | {
115 | $last{$row->{'v_server'}} = $row->{'lu'};
116 | }
117 |
118 | my $last_o;
119 | $sth = $db->prepare('SELECT MAX(changed) as lu FROM ovms_owners');
120 | $sth->execute();
121 | while (my $row = $sth->fetchrow_hashref())
122 | {
123 | $last_o = $row->{'lu'};
124 | }
125 |
126 | foreach (keys %svr_conns)
127 | {
128 | my $vehicleid = $_;
129 | my $fn = $svr_conns{$vehicleid};
130 | my $svrupdate_v = $conns{$fn}{'svrupdate_v'};
131 | my $svrupdate_o = $conns{$fn}{'svrupdate_o'};
132 | my $lw = $last{'*'}; $lw='0000-00-00 00:00:00' if (!defined $lw);
133 | my $ls = $last{$vehicleid}; $ls='0000-00-00 00:00:00' if (!defined $ls);
134 | if (($lw gt $svrupdate_v)||($ls gt $svrupdate_v)||($last_o gt $svrupdate_o))
135 | {
136 | &svr_push($fn,$vehicleid);
137 | }
138 | }
139 | }
140 |
141 | sub svr_tim2
142 | {
143 | if ((!defined $svr_handle)&&(defined $svr_server))
144 | {
145 | &svr_client();
146 | }
147 | }
148 |
149 | sub svr_push
150 | {
151 | my ($fn,$vehicleid) = @_;
152 |
153 | # Push updated cars to the specified server
154 | return if (!defined $svr_conns{$vehicleid}); # Make sure it is a server
155 |
156 | my $sth = $db->prepare('SELECT * FROM ovms_cars WHERE v_type="CAR" AND v_server IN ("*",?) AND changed>? ORDER BY changed');
157 | $sth->execute($vehicleid,$conns{$fn}{'svrupdate_v'});
158 | while (my $row = $sth->fetchrow_hashref())
159 | {
160 | &io_tx($fn, $conns{$fn}{'handle'}, 'RV',
161 | join(',',$row->{'vehicleid'},$row->{'owner'},$row->{'carpass'},
162 | $row->{'v_server'},$row->{'deleted'},$row->{'changed'}));
163 | $conns{$fn}{'svrupdate_v'} = $row->{'changed'};
164 | }
165 |
166 | $sth = $db->prepare('SELECT * FROM ovms_owners WHERE changed>? ORDER BY changed');
167 | $sth->execute($conns{$fn}{'svrupdate_o'});
168 | while (my $row = $sth->fetchrow_hashref())
169 | {
170 | &io_tx($fn, $conns{$fn}{'handle'}, 'RO',
171 | join(',',$row->{'owner'},$row->{'name'},$row->{'mail'},
172 | $row->{'pass'},$row->{'status'},$row->{'deleted'},$row->{'changed'}));
173 | $conns{$fn}{'svrupdate_o'} = $row->{'changed'};
174 | }
175 | }
176 |
177 | sub svr_client
178 | {
179 | tcp_connect $svr_server, $svr_port, sub
180 | {
181 | my ($fh) = @_;
182 |
183 | $svr_handle = new AnyEvent::Handle(fh => $fh, on_error => \&svr_error, on_rtimeout => \&svr_timeout, keepalive => 1, no_delay => 1, rtimeout => 60*60);
184 | $svr_handle->push_read (line => \&svr_welcome);
185 |
186 | my $sth = $db->prepare('SELECT MAX(changed) AS mc FROM ovms_cars WHERE v_type="CAR"');
187 | $sth->execute();
188 | my $row = $sth->fetchrow_hashref();
189 | my $last_v = $row->{'mc'}; $last_v = '0000-00-00 00:00:00' if (!defined $last_v);
190 |
191 | $sth = $db->prepare('SELECT MAX(changed) AS mc FROM ovms_owners');
192 | $sth->execute();
193 | $row = $sth->fetchrow_hashref();
194 | my $last_o = $row->{'mc'}; $last_o = '0000-00-00 00:00:00' if (!defined $last_o);
195 |
196 | $svr_client_token = '';
197 | foreach (0 .. 21)
198 | { $svr_client_token .= substr($b64tab,rand(64),1); }
199 | my $client_hmac = Digest::HMAC->new($svr_pass, "Digest::MD5");
200 | $client_hmac->add($svr_client_token);
201 | $svr_client_digest = $client_hmac->b64digest();
202 | $svr_handle->push_write("MP-S 0 $svr_client_token $svr_client_digest $svr_vehicle $last_v $last_o\r\n");
203 | }
204 | }
205 |
206 | sub svr_welcome
207 | {
208 | my ($hdl, $line) = @_;
209 |
210 | my $fn = $hdl->fh->fileno();
211 | AE::log info => "#$fn - - svr welcome $line";
212 |
213 | my ($welcome,$crypt,$server_token,$server_digest) = split /\s+/,$line;
214 |
215 | my $d_server_digest = decode_base64($server_digest);
216 | my $client_hmac = Digest::HMAC->new($svr_pass, "Digest::MD5");
217 | $client_hmac->add($server_token);
218 | if ($client_hmac->digest() ne $d_server_digest)
219 | {
220 | AE::log error => "#$fn - - svr server digest is invalid - aborting";
221 | undef $svr_handle;
222 | return;
223 | }
224 |
225 | $client_hmac = Digest::HMAC->new($svr_pass, "Digest::MD5");
226 | $client_hmac->add($server_token);
227 | $client_hmac->add($svr_client_token);
228 | my $client_key = $client_hmac->digest;
229 |
230 | $svr_txcipher = Crypt::RC4::XS->new($client_key);
231 | $svr_txcipher->RC4(chr(0) x 1024); # Prime the cipher
232 | $svr_rxcipher = Crypt::RC4::XS->new($client_key);
233 | $svr_rxcipher->RC4(chr(0) x 1024); # Prime the cipher
234 |
235 | $svr_handle->push_read (line => \&svr_line);
236 | }
237 |
238 | sub svr_line
239 | {
240 | my ($hdl, $line) = @_;
241 | my $fn = $hdl->fh->fileno();
242 |
243 | $svr_handle->push_read (line => \&svr_line);
244 |
245 | my $dline = $svr_rxcipher->RC4(decode_base64($line));
246 | AE::log debug => "#$fn - - svr got $dline";
247 |
248 | if ($dline =~ /^MP-0 A/)
249 | {
250 | $svr_handle->push_write(encode_base64($svr_txcipher->RC4("MP-0 a"),''));
251 | }
252 | elsif ($dline =~ /MP-0 RV(.+)/)
253 | {
254 | my ($vehicleid,$owner,$carpass,$v_server,$deleted,$changed) = split(/,/,$1);
255 | AE::log info => "#$fn - - svr got vehicle record update $vehicleid ($changed)";
256 |
257 | $db->do('INSERT INTO ovms_cars (vehicleid,owner,carpass,v_server,deleted,changed,v_lastupdate) '
258 | . 'VALUES (?,?,?,?,?,?,NOW()) '
259 | . 'ON DUPLICATE KEY UPDATE owner=?, carpass=?, v_server=?, deleted=?, changed=?',
260 | undef,
261 | $vehicleid,$owner,$carpass,$v_server,$deleted,$changed,$owner,$carpass,$v_server,$deleted,$changed);
262 | }
263 | elsif ($dline =~ /MP-0 RO(.+)/)
264 | {
265 | my ($owner,$name,$mail,$pass,$status,$deleted,$changed) = split(/,/,$1);
266 | AE::log info => "#$fn - - svr got owner record update $owner ($changed)";
267 |
268 | $db->do('INSERT INTO ovms_owners (owner,name,mail,pass,status,deleted,changed) '
269 | . 'VALUES (?,?,?,?,?,?,?) '
270 | . 'ON DUPLICATE KEY UPDATE name=?, mail=?, pass=?, status=?, deleted=?, changed=?',
271 | undef,
272 | $owner,$name,$mail,$pass,$status,$deleted,$changed,
273 | $name,$mail,$pass,$status,$deleted,$changed);
274 | }
275 | }
276 |
277 | sub svr_error
278 | {
279 | my ($hdl, $fatal, $msg) = @_;
280 | my $fn = $hdl->fh->fileno();
281 |
282 | AE::log note => "#$fn - - svr got disconnect from remote";
283 |
284 | undef $svr_handle;
285 | }
286 |
287 | sub svr_timeout
288 | {
289 | my ($hdl) = @_;
290 | my $fn = $hdl->fh->fileno();
291 |
292 | AE::log note => "#$fn - - svr got timeout from remote";
293 |
294 | undef $svr_handle;
295 | }
296 |
297 | 1;
298 |
--------------------------------------------------------------------------------
/v3/server/plugins/system/OVMS/Server/ApiHttp.pm:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 |
3 | ########################################################################
4 | # OVMS Server HTTP API plugin
5 | #
6 | # This plugin provides base support for a HTTP API server listening
7 | # on ports tcp/6868 (plain HTTP) and tcp/6869 (SSL/TLS HTTPS). It is
8 | # a base requirement of the other ApiHttp* plugins that provide the
9 | # actual API methods.
10 |
11 | package OVMS::Server::ApiHttp;
12 |
13 | use strict;
14 | use warnings;
15 | use Carp;
16 |
17 | use AnyEvent;
18 | use AnyEvent::Log;
19 | use AnyEvent::HTTPD;
20 |
21 | use OVMS::Server::Core;
22 | use OVMS::Server::Plugin;
23 |
24 | use Exporter qw(import);
25 |
26 | our @EXPORT = qw();
27 |
28 | # API: HTTP
29 |
30 | my $me; # Reference to our singleton object
31 | my $httpapi_conns_count = 0; # Count of the number of HTTP API connections
32 | my $http_server; # HTTP Server handle
33 | my $https_server; # HTTPS Server handle
34 | my %registrations = (); # URL registrations
35 | my $request_ticker_w; # A period request ticker
36 |
37 | use vars qw{
38 | };
39 |
40 | sub new
41 | {
42 | my $class = shift;
43 | $class = ref($class) || $class;
44 | my $self = {@_};
45 | bless( $self, $class );
46 |
47 | $me = $self;
48 |
49 | register_callback('', \&http_request_in_root);
50 |
51 | RegisterFunction('HttpServerRegisterCallback', \®ister_callback);
52 | RegisterEvent('StartRun', \&start);
53 |
54 | return $self;
55 | }
56 |
57 | sub start
58 | {
59 | AE::log info => "- - - starting HTTP server listener on port tcp/6868";
60 | $http_server = AnyEvent::HTTPD->new (port => 6868, request_timeout => 30, allowed_methods => ['GET','PUT','POST','DELETE']);
61 | $http_server->reg_cb ( %registrations );
62 |
63 | $http_server->reg_cb (
64 | client_connected => sub {
65 | my ($httpd, $host, $port) = @_;
66 | $httpapi_conns_count++;
67 | FunctionCall('InfoCount', 'HTTPAPI_conns', $httpapi_conns_count);
68 | AE::log info => join(' ','http','-','-',$host.':'.$port,'connect');
69 | }
70 | );
71 |
72 | $http_server->reg_cb (
73 | client_disconnected => sub {
74 | my ($httpd, $host, $port) = @_;
75 | $httpapi_conns_count--;
76 | FunctionCall('InfoCount', 'HTTPAPI_conns', $httpapi_conns_count);
77 | AE::log info => join(' ','http','-','-',$host.':'.$port,'disconnect');
78 | }
79 | );
80 |
81 | $http_server->reg_cb (request => \&request);
82 |
83 | my $pemfile = MyConfig()->val('httpapi','sslcrt','conf/ovms_server.pem');
84 | if (-e $pemfile)
85 | {
86 | AE::log info => "- - - starting HTTPS server listener on port tcp/6869";
87 | $https_server = AnyEvent::HTTPD->new (port => 6869, request_timeout => 30, ssl => { cert_file => $pemfile }, allowed_methods => ['GET','PUT','POST','DELETE']);
88 | $https_server->reg_cb ( %registrations );
89 |
90 | $https_server->reg_cb (
91 | client_connected => sub {
92 | my ($httpd, $host, $port) = @_;
93 | $httpapi_conns_count++;
94 | FunctionCall('InfoCount', 'HTTPAPI_conns', $httpapi_conns_count);
95 | AE::log info => join(' ','http','-','-',$host.':'.$port,'connect(ssl)');
96 | }
97 | );
98 |
99 | $https_server->reg_cb (
100 | client_disconnected => sub {
101 | my ($httpd, $host, $port) = @_;
102 | $httpapi_conns_count--;
103 | FunctionCall('InfoCount', 'HTTPAPI_conns', $httpapi_conns_count);
104 | AE::log info => join(' ','http','-','-',$host.':'.$port,'disconnect(ssl)');
105 | }
106 | );
107 |
108 | $https_server->reg_cb (request => \&request);
109 | }
110 | $request_ticker_w = AnyEvent->timer (after => 60, interval => 60, cb => \&request_ticker);
111 | }
112 |
113 | ########################################################
114 | # API HTTP request handler
115 | #
116 | # Attempts to rate-limit requests to ensure fair
117 | # delivery of services
118 |
119 | my %ratelimit;
120 | sub request
121 | {
122 | my ($httpd, $req) = @_;
123 | my $host = $req->client_host;
124 | my $port = $req->client_port;
125 | my $key = $host . ':' . $port;
126 |
127 | my $burst = MyConfig()->val('httpapi','ratelimit_http_burst','120');
128 | my $delay = MyConfig()->val('httpapi','ratelimit_http_delay','20');
129 | my $expire = MyConfig()->val('httpapi','ratelimit_http_expire','300');
130 |
131 | if (!defined $ratelimit{$host})
132 | {
133 | # Initial allocation
134 | $ratelimit{$host}{'quota'} = $burst;
135 | }
136 | else
137 | {
138 | $ratelimit{$host}{'quota'}--;
139 | $ratelimit{$host}{'quota'}=0 if ($ratelimit{$host}{'quota'}<0);
140 | }
141 | AE::log info => join(' ','http','-','-',$host.':'.$port,'quota',$ratelimit{$host}{'quota'});
142 | $ratelimit{$host}{'expire'} = time + $expire;
143 |
144 | if ($ratelimit{$host}{'quota'} <= 0)
145 | {
146 | # Need to reject the call
147 | $httpd->stop_request;
148 | $ratelimit{$host}{'timers'}{$port} = AnyEvent->timer (after => $delay, cb => sub
149 | {
150 | delete $ratelimit{$host}{'timers'}{$port} if (defined $ratelimit{$host});
151 | my $url = $req->url;
152 | $url = $1 if ($url =~ /^(.+)\?.*$/);
153 | AE::log info => join(' ','http','-','-',$host.':'.$port,'too many requests (delayed)',$req->method,$url);
154 | $req->respond([429, "Too many requests"]);
155 | });
156 | }
157 | }
158 |
159 | sub request_ticker
160 | {
161 | my $now = time;
162 |
163 | my $permin = MyConfig()->val('httpapi','ratelimit_http_permin','60');
164 | my $max = MyConfig()->val('httpapi','ratelimit_http_max','240');
165 |
166 | foreach my $host (keys %ratelimit)
167 | {
168 | if ($ratelimit{$host}{'expire'} <= $now)
169 | {
170 | delete $ratelimit{$host};
171 | }
172 | else
173 | {
174 | $ratelimit{$host}{'quota'} += $permin;
175 | $ratelimit{$host}{'quota'} = $max if ($ratelimit{$host}{'quota'} > $max);
176 | }
177 | }
178 | }
179 |
180 | ########################################################
181 | # API HTTP server
182 | #
183 |
184 | sub register_callback
185 | {
186 | my ($url, $cb) = @_;
187 |
188 | my ($caller) = caller;
189 | ($caller) = caller(1) if ($caller eq 'OVMS::Server::Plugin');
190 |
191 | AE::log info => "- - - register callback url '$url' for $caller";
192 |
193 | $registrations{$url} = $cb;
194 | }
195 |
196 | sub http_request_in_root
197 | {
198 | my ($httpd, $req) = @_;
199 |
200 | AE::log info => join(' ','http','-','-',$req->client_host.':'.$req->client_port,'-',$req->method,$req->url);
201 |
202 | $req->respond ( [404, 'not found', { 'Content-Type' => 'text/plain', 'Access-Control-Allow-Origin' => '*' }, "not found\n"] );
203 | $httpd->stop_request;
204 | }
205 |
206 | 1;
207 |
--------------------------------------------------------------------------------
/v3/server/plugins/system/OVMS/Server/ApiHttpFile.pm:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 |
3 | ########################################################################
4 | # HTTP API FILE functions plugin
5 | #
6 | # This plugin provides the core HTTP API methods for file download from
7 | # a public directory. It requires the ApiHttp plugin to be previously loaded.
8 |
9 |
10 | package OVMS::Server::ApiHttpFile;
11 |
12 | use strict;
13 | use warnings;
14 | use Carp;
15 |
16 | use AnyEvent;
17 | use AnyEvent::Log;
18 | use OVMS::Server::Plugin;
19 |
20 | use Exporter qw(import);
21 |
22 | our @EXPORT = qw();
23 |
24 | # API: HTTP
25 |
26 | my $me; # Reference to our singleton object
27 |
28 | if (!PluginLoaded('ApiHttp'))
29 | {
30 | AE::log error => "Error: ApiHttp MUST be loaded before this plugin";
31 | }
32 |
33 | use vars qw{
34 | };
35 |
36 | sub new
37 | {
38 | my $class = shift;
39 | $class = ref($class) || $class;
40 | my $self = {@_};
41 | bless( $self, $class );
42 |
43 | $me = $self;
44 | $self->init();
45 |
46 | FunctionCall('HttpServerRegisterCallback','/file', \&http_request_in_file);
47 |
48 | return $self;
49 | }
50 |
51 | sub init
52 | {
53 | my ($self) = @_;
54 | }
55 |
56 | ########################################################
57 | # API HTTP server plugin methods
58 |
59 | sub http_request_in_file
60 | {
61 | my ($httpd, $req) = @_;
62 |
63 | AE::log info => join(' ','http','-','-',$req->client_host.':'.$req->client_port,'-',$req->method,$req->url);
64 |
65 | my $filepath = $1 if ($req->url =~ /^\/file\/([a-zA-Z0-9\-\_\.]+)$/);
66 |
67 | if ((defined $filepath)&&(-f "httpfiles/$filepath"))
68 | {
69 | open my $fp,'<',"httpfiles/$filepath";
70 | $_ = <$fp>; chop;
71 | my $contenttype = $_;
72 | AE::log info => join(' ','http','-','-',$req->client_host.':'.$req->client_port,'-','start file transfer');
73 | $req->respond ({ content => [$contenttype, sub {
74 | my ($data_cb) = @_;
75 |
76 | if (!defined $data_cb)
77 | {
78 | AE::log info => join(' ','http','-','-',$req->client_host.':'.$req->client_port,'-','finished file transfer');
79 | close $fp;
80 | return;
81 | }
82 | else
83 | {
84 | my $buf;
85 | read $fp,$buf,16384;
86 | AE::log info => join(' ','http','-','-',$req->client_host.':'.$req->client_port,'-','file transfer blob: ',length($buf),'bytes');
87 | &$data_cb($buf);
88 | }
89 | } ]});
90 | }
91 | else
92 | {
93 | $req->respond (
94 | [404, 'not found', { 'Content-Type' => 'text/plain' }, "not found\n"]
95 | );
96 | }
97 |
98 | $httpd->stop_request;
99 | }
100 |
101 | 1;
102 |
--------------------------------------------------------------------------------
/v3/server/plugins/system/OVMS/Server/ApiHttpMqapi.pm:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 |
3 | ########################################################################
4 | # HTTP API MQAPI functions plugin
5 | #
6 | # This plugin provides the core HTTP API methods for Mosquitto
7 | # authentication. It requires the ApiHttp plugin to be previously loaded.
8 |
9 | package OVMS::Server::ApiHttpMqapi;
10 |
11 | use strict;
12 | use warnings;
13 | use Carp;
14 |
15 | use AnyEvent;
16 | use AnyEvent::Log;
17 | use OVMS::Server::Core;
18 | use OVMS::Server::Plugin;
19 |
20 | use Exporter qw(import);
21 |
22 | our @EXPORT = qw();
23 |
24 | # API: HTTP
25 |
26 | my $me; # Reference to our singleton object
27 | my $mqtt_superuser; # Superuser defined in the config
28 |
29 | if (!PluginLoaded('ApiHttp'))
30 | {
31 | AE::log error => "Error: ApiHttp MUST be loaded before this plugin";
32 | }
33 |
34 | use vars qw{
35 | };
36 |
37 | sub new
38 | {
39 | my $class = shift;
40 | $class = ref($class) || $class;
41 | my $self = {@_};
42 | bless( $self, $class );
43 |
44 | $me = $self;
45 | $self->init();
46 |
47 | $mqtt_superuser = MyConfig()->val('mqtt','superuser'); # MQTT superuser
48 |
49 | FunctionCall('HttpServerRegisterCallback','/mqapi/auth', \&http_request_in_mqapi_auth);
50 | FunctionCall('HttpServerRegisterCallback','/mqapi/superuser', \&http_request_in_mqapi_superuser);
51 | FunctionCall('HttpServerRegisterCallback','/mqapi/acl', \&http_request_in_mqapi_acl);
52 |
53 | return $self;
54 | }
55 |
56 | sub init
57 | {
58 | my ($self) = @_;
59 | }
60 |
61 | ########################################################
62 | # API HTTP server plugin methods
63 |
64 | sub http_request_in_mqapi_auth
65 | {
66 | my ($httpd, $req) = @_;
67 |
68 | my $method = $req->method;
69 | if ($method ne 'POST')
70 | {
71 | $req->respond ( [404, 'Unrecongised API call', { 'Content-Type' => 'text/plain', 'Access-Control-Allow-Origin' => '*' }, "Unrecognised API call\n"] );
72 | $httpd->stop_request;
73 | return;
74 | }
75 |
76 | my $p_username = $req->parm('username');
77 | my $p_password = $req->parm('password');
78 | my $p_topic = $req->parm('topic');
79 | my $p_acc = $req->parm('acc');
80 |
81 | if ((defined $p_username)&&(defined $p_password))
82 | {
83 | my $permissions = FunctionCall('Authenticate',$p_username,$p_password);
84 | if (($permissions ne '')&&(IsPermitted($permissions,'v3','mqtt')))
85 | {
86 | AE::log info => join(' ','http','-','-',$req->client_host.':'.$req->client_port,'mqapi/auth',$p_username,'SUCCESS');
87 | $req->respond ( [200, 'Authentication OK', { 'Content-Type' => 'text/plain', 'Access-Control-Allow-Origin' => '*' }, ''] );
88 | $httpd->stop_request;
89 | return;
90 | }
91 | }
92 |
93 | AE::log info => join(' ','http','-','-',$req->client_host.':'.$req->client_port,'mqapi/auth',$p_username,'FAILED');
94 | $req->respond ( [403, 'Authentication FAILED', { 'Content-Type' => 'text/plain', 'Access-Control-Allow-Origin' => '*' }, ''] );
95 | $httpd->stop_request;
96 | }
97 |
98 | sub http_request_in_mqapi_superuser
99 | {
100 | my ($httpd, $req) = @_;
101 |
102 | my $method = $req->method;
103 | if ($method ne 'POST')
104 | {
105 | $req->respond ( [404, 'Unrecongised API call', { 'Content-Type' => 'text/plain', 'Access-Control-Allow-Origin' => '*' }, "Unrecognised API call\n"] );
106 | $httpd->stop_request;
107 | return;
108 | }
109 |
110 | my $p_username = $req->parm('username');
111 |
112 | if ((!defined $p_username)||($p_username eq ''))
113 | {
114 | $req->respond ( [404, 'Username required', { 'Content-Type' => 'text/plain', 'Access-Control-Allow-Origin' => '*' }, "Username required\n"] );
115 | $httpd->stop_request;
116 | return;
117 | }
118 |
119 | if ((!defined $mqtt_superuser)||($mqtt_superuser eq ''))
120 | {
121 | AE::log info => join(' ','http','-','-',$req->client_host.':'.$req->client_port,'mqapi/superuser',$p_username,'NOSUPERUSER');
122 | $req->respond ( [403, 'Not a superuser', { 'Content-Type' => 'text/plain', 'Access-Control-Allow-Origin' => '*' }, ''] );
123 | }
124 | else
125 | {
126 | if ((defined $p_username)&&($p_username eq $mqtt_superuser))
127 | {
128 | AE::log info => join(' ','http','-','-',$req->client_host.':'.$req->client_port,'mqapi/superuser',$p_username,'SUPERUSER');
129 | $req->respond ( [200, 'Is a superuser', { 'Content-Type' => 'text/plain', 'Access-Control-Allow-Origin' => '*' }, ''] );
130 | }
131 | else
132 | {
133 | AE::log info => join(' ','http','-','-',$req->client_host.':'.$req->client_port,'mqapi/superuser',$p_username,'NORMALUSER');
134 | $req->respond ( [403, 'Not a superuser', { 'Content-Type' => 'text/plain', 'Access-Control-Allow-Origin' => '*' }, ''] );
135 | }
136 | }
137 |
138 | $httpd->stop_request;
139 | }
140 |
141 | sub http_request_in_mqapi_acl
142 | {
143 | my ($httpd, $req) = @_;
144 |
145 | my $method = $req->method;
146 | if ($method ne 'POST')
147 | {
148 | $req->respond ( [404, 'Unrecongised API call', { 'Content-Type' => 'text/plain', 'Access-Control-Allow-Origin' => '*' }, "Unrecognised API call\n"] );
149 | $httpd->stop_request;
150 | return;
151 | }
152 |
153 | my $p_username = $req->parm('username');
154 | my $p_topic = $req->parm('topic');
155 | my $p_clientid = $req->parm('clientid');
156 | my $p_acc = $req->parm('acc');
157 |
158 | if ((defined $mqtt_superuser)&&($mqtt_superuser ne '')&&
159 | (defined $p_username)&&($p_username eq $mqtt_superuser))
160 | {
161 | AE::log info => join(' ','http','-','-',$req->client_host.':'.$req->client_port,'mqapi/acl',$p_username, $p_topic,'PERMIT');
162 | $req->respond ( [200, 'Superuser acl is permitted', { 'Content-Type' => 'text/plain', 'Access-Control-Allow-Origin' => '*' }, ''] );
163 | $httpd->stop_request;
164 | return;
165 | }
166 |
167 | if ((defined $p_username)&&(defined $p_topic)&&
168 | (substr($p_topic,0,length($p_username)+6) eq 'ovms/'.$p_username.'/'))
169 | {
170 | AE::log info => join(' ','http','-','-',$req->client_host.':'.$req->client_port,'mqapi/acl',$p_username, $p_topic, 'PERMIT');
171 | $req->respond ( [200, 'Access granted', { 'Content-Type' => 'text/plain', 'Access-Control-Allow-Origin' => '*' }, ''] );
172 | }
173 | else
174 | {
175 | AE::log info => join(' ','http','-','-',$req->client_host.':'.$req->client_port,'mqapi/acl',$p_username, $p_topic, 'DENY');
176 | $req->respond ( [403, 'Access denied', { 'Content-Type' => 'text/plain', 'Access-Control-Allow-Origin' => '*' }, ''] );
177 | }
178 |
179 | $httpd->stop_request;
180 | }
181 |
182 | 1;
183 |
--------------------------------------------------------------------------------
/v3/server/plugins/system/OVMS/Server/AuthDrupal.pm:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 |
3 | ########################################################################
4 | # Authentication via Drupal
5 | #
6 | # This plugin provides for authentication via Drupal database.
7 | # Note: Only one Auth* plugin should be loaded at any one time.
8 |
9 | package OVMS::Server::AuthDrupal;
10 |
11 | use strict;
12 | use warnings;
13 | use Carp;
14 |
15 | use AnyEvent;
16 | use AnyEvent::Log;
17 | use Digest::SHA qw(sha256 sha512);
18 | use OVMS::Server::Core;
19 | use OVMS::Server::Plugin;
20 |
21 | use Exporter qw(import);
22 |
23 | our @EXPORT = qw();
24 |
25 | # Authentication: Drupal (authenticate against drupal)
26 |
27 | my $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
28 | my $me; # Reference to our singleton object
29 | my $drupaltim;
30 | my $drupal_interval;
31 |
32 | use vars qw{
33 | $config
34 | };
35 |
36 | sub new
37 | {
38 | my $class = shift;
39 | $class = ref($class) || $class;
40 | my $self = {@_};
41 | bless( $self, $class );
42 |
43 | $me = $self;
44 | $self->init();
45 |
46 | RegisterFunction('Authenticate',\&Authenticate);
47 |
48 | $drupal_interval = MyConfig()->val('drupal','interval',60);
49 | $drupaltim = AnyEvent->timer (after => $drupal_interval, interval => $drupal_interval, cb => \&drupal_tim);
50 |
51 | return $self;
52 | }
53 |
54 | sub init
55 | {
56 | my ($self) = @_;
57 | }
58 |
59 | sub Authenticate
60 | {
61 | my ($user,$password) = @_;
62 |
63 | my $rec = FunctionCall('DbGetOwner',$user);
64 | return '' if (!defined $rec); # Authentication fail if user record not found
65 |
66 | # Check user password authentication
67 | my $dbpass = $rec->{'pass'};
68 | return '' if (!defined $dbpass); # Authentication fails if no password
69 |
70 | my $iter_log2 = index($itoa64,substr($dbpass,3,1));
71 | my $iter_count = 1 << $iter_log2;
72 |
73 | my $phash = substr($dbpass,0,12);
74 | my $salt = substr($dbpass,4,8);
75 |
76 | my $hash = sha512($salt.$password);
77 | do
78 | {
79 | $hash = sha512($hash.$password);
80 | $iter_count--;
81 | } while ($iter_count > 0);
82 |
83 | my $encoded = substr($phash . &drupal_password_base64_encode($hash,length($hash)),0,55);
84 |
85 | if ($encoded eq $dbpass)
86 | {
87 | # Full permissions for a user+pass authentication
88 | AE::log debug => '- - - Authentication via drupal username+password';
89 | return '*';
90 | }
91 |
92 | # Check api token authentication
93 | $rec = FunctionCall('DbGetToken',$user,$password);
94 | if (defined $rec)
95 | {
96 | AE::log debug => '- - - Authentication via drupal username+apitoken';
97 | return $rec->{'permit'};
98 | }
99 |
100 | # Otherwise, authentication failed
101 | return '';
102 | }
103 |
104 | sub drupal_password_base64_encode
105 | {
106 | my ($input, $count) = @_;
107 | my $output = '';
108 | my $i = 0;
109 | do
110 | {
111 | my $value = ord(substr($input,$i++,1));
112 | $output .= substr($itoa64,$value & 0x3f,1);
113 | if ($i < $count)
114 | {
115 | $value |= ord(substr($input,$i,1)) << 8;
116 | $output .= substr($itoa64,($value >> 6) & 0x3f,1);
117 | }
118 |
119 | $i++;
120 | if ($i < $count)
121 | {
122 | $value |= ord(substr($input,$i,1)) << 16;
123 | $output .= substr($itoa64,($value >> 12) & 0x3f,1);
124 | }
125 |
126 | $i++;
127 | if ($i < $count)
128 | {
129 | $output .= substr($itoa64,($value >> 18) & 0x3f,1);
130 | }
131 | } while ($i < $count);
132 |
133 | return $output;
134 | }
135 |
136 | sub drupal_tim
137 | {
138 | # Periodic drupal maintenance
139 |
140 | AE::log info => '- - - Periodic Drupal maintenance';
141 |
142 | FunctionCall('DbDoSQL',
143 | 'INSERT INTO ovms_owners SELECT uid,name,mail,pass,status,0,utc_timestamp() FROM users '
144 | . 'WHERE users.uid NOT IN (SELECT owner FROM ovms_owners)');
145 |
146 | FunctionCall('DbDoSQL',
147 | 'UPDATE ovms_owners LEFT JOIN users ON users.uid=ovms_owners.owner '
148 | . 'SET ovms_owners.pass=users.pass, ovms_owners.status=users.status, ovms_owners.name=users.name, ovms_owners.mail=users.mail, '
149 | . ' ovms_owners.deleted=0, ovms_owners.changed=UTC_TIMESTAMP() '
150 | . 'WHERE users.pass<>ovms_owners.pass OR users.status<>ovms_owners.status OR users.name<>ovms_owners.name OR users.mail<>ovms_owners.mail');
151 |
152 | FunctionCall('DbDoSQL',
153 | 'UPDATE ovms_owners SET deleted=1,changed=UTC_TIMESTAMP() WHERE deleted=0 AND owner NOT IN (SELECT uid FROM users)');
154 | }
155 |
156 | 1;
157 |
--------------------------------------------------------------------------------
/v3/server/plugins/system/OVMS/Server/AuthNone.pm:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 |
3 | ########################################################################
4 | # Authentication Stub
5 | #
6 | # This plugin provides a stub implementation for authentication
7 | # providers. It does nothing and will always deny authentication attempts.
8 | # Note: Only one Auth* plugin should be loaded at any one time.
9 |
10 | package OVMS::Server::AuthNone;
11 |
12 | use strict;
13 | use warnings;
14 | use Carp;
15 |
16 | use AnyEvent;
17 | use AnyEvent::Log;
18 | use OVMS::Server::Plugin;
19 |
20 | use Exporter qw(import);
21 |
22 | our @EXPORT = qw();
23 |
24 | # Authentication: None (stub, everything fails)
25 |
26 | my $me; # Reference to our singleton object
27 |
28 | use vars qw{
29 | $config
30 | };
31 |
32 | sub new
33 | {
34 | my $class = shift;
35 | $class = ref($class) || $class;
36 | my $self = {@_};
37 | bless( $self, $class );
38 |
39 | $me = $self;
40 | $self->init();
41 |
42 | RegisterFunction('Authenticate',\&Authenticate);
43 |
44 | return $self;
45 | }
46 |
47 | sub init
48 | {
49 | my ($self) = @_;
50 | }
51 |
52 | sub Authenticate
53 | {
54 | my ($user,$password) = @_;
55 |
56 | return ''; # Authentication always fails
57 | }
58 |
59 | 1;
60 |
--------------------------------------------------------------------------------
/v3/server/plugins/system/OVMS/Server/Core.pm:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 |
3 | ########################################################################
4 | # OVMS Server Core
5 | #
6 | # This plugin provides the core of the OVMS system, and is always
7 | # automatically loaded.
8 |
9 | package OVMS::Server::Core;
10 |
11 | use strict;
12 | use warnings;
13 | use Carp;
14 |
15 | use AnyEvent;
16 | use AnyEvent::Log;
17 | use Config::IniFiles;
18 | use POSIX qw(strftime);
19 |
20 | use Exporter qw(import);
21 |
22 | our @EXPORT = qw(
23 | MyConfig IsPermitted UTCDate UTCDateFull UTCTime GetVersion
24 | ConnStart ConnFinish
25 | ConnGetAttribute ConnGetAttributeRef ConnHasAttribute
26 | ConnSetAttribute ConnSetAttributes ConnIncAttribute
27 | ConnDefined ConnKeys
28 | CarConnect CarDisconnect CarConnectionCount CarConnection
29 | AppConnect AppDisconnect AppConnectionCount AppConnections AppConnection
30 | BatchConnect BatchDisconnect BatchConnectionCount BatchConnections
31 | ClientConnections
32 | ConnTransmit CarTransmit ClientsTransmit ConnShutdown
33 | );
34 |
35 | my $VERSION;
36 | if (-e '/usr/bin/git')
37 | { $VERSION = `/usr/bin/git describe --always --tags --dirty`; chop $VERSION; }
38 | else
39 | { $VERSION = '3.0.0-custom'; }
40 |
41 | my $me; # Reference to our singleton object
42 |
43 | ########################################################################
44 | # Initialisation
45 |
46 | use vars qw{
47 | $config
48 | };
49 |
50 | sub new
51 | {
52 | my $class = shift;
53 | $class = ref($class) || $class;
54 | my $self = {@_};
55 | bless( $self, $class );
56 |
57 | $me = $self;
58 | $self->init();
59 |
60 | return $self;
61 | }
62 |
63 | sub init
64 | {
65 | my ($self) = @_;
66 |
67 | $self->{'config'} = Config::IniFiles->new(-file => 'conf/ovms_server.conf');
68 | }
69 |
70 | ########################################################################
71 | # Server version
72 |
73 | sub GetVersion
74 | {
75 | return $VERSION;
76 | }
77 |
78 | ########################################################################
79 | # Access to configuration object
80 |
81 | sub MyConfig
82 | {
83 | return $me->{'config'};
84 | }
85 |
86 | ########################################################################
87 | # Permissions helpers
88 |
89 | sub IsPermitted
90 | {
91 | my ($permissions, @rights) = @_;
92 |
93 | return 1 if ($permissions eq '*');
94 |
95 | my %ph = map { lc($_) => 1 } split(/\s*,\s*/,$permissions);
96 |
97 | foreach my $right (@rights)
98 | {
99 | return 1 if (defined $ph{lc($right)});
100 | }
101 |
102 | return 0;
103 | }
104 |
105 | ########################################################################
106 | # Date/time helpers
107 |
108 | sub UTCDate
109 | {
110 | my ($t) = @_;
111 | $t = time if (!defined $t);
112 |
113 | return strftime "%Y-%m-%d", gmtime($t);
114 | }
115 |
116 | sub UTCDateFull
117 | {
118 | my ($t) = @_;
119 | $t = time if (!defined $t);
120 |
121 | return strftime "%Y-%m-%d 00:00:00", gmtime($t);
122 | }
123 |
124 | sub UTCTime
125 | {
126 | my ($t) = @_;
127 | $t = time if (!defined $t);
128 |
129 | return strftime "%Y-%m-%d %H:%M:%S", gmtime($t);
130 | }
131 |
132 | ########################################################################
133 | # Connection Registry
134 |
135 | my %conns; # Connection informaton (keyed by fd#)
136 |
137 | sub ConnStart
138 | {
139 | my ($fn, %attr) = @_;
140 |
141 | AE::log info => "#$fn - - ConnStart";
142 |
143 | delete $conns{$fn}; # Clean-up any residual data for this connection
144 |
145 | foreach my $key (keys %attr)
146 | {
147 | $conns{$fn}{$key} = $attr{$key};
148 | }
149 | }
150 |
151 | sub ConnFinish
152 | {
153 | my ($fn) = @_;
154 |
155 | AE::log info => "#$fn - - ConnFinish";
156 |
157 | delete $conns{$fn};
158 | }
159 |
160 | sub ConnGetAttribute
161 | {
162 | my ($fn, $key) = @_;
163 |
164 | return ((defined $conns{$fn})&&(defined $conns{$fn}{$key}))
165 | ?$conns{$fn}{$key}
166 | :undef;
167 | }
168 |
169 | sub ConnGetAttributeRef
170 | {
171 | my ($fn, $key) = @_;
172 |
173 | return \$conns{$fn}{$key};
174 | }
175 |
176 | sub ConnHasAttribute
177 | {
178 | my ($fn, $key) = @_;
179 |
180 | return ((defined $conns{$fn})&&(defined $conns{$fn}{$key}));
181 | }
182 |
183 | sub ConnSetAttribute
184 | {
185 | my ($fn, $key, $value) = @_;
186 |
187 | $conns{$fn}{$key} = $value;
188 | }
189 |
190 | sub ConnSetAttributes
191 | {
192 | my ($fn, %attr) = @_;
193 |
194 | foreach my $key (keys %attr)
195 | {
196 | $conns{$fn}{$key} = $attr{$key};
197 | }
198 | }
199 |
200 | sub ConnIncAttribute
201 | {
202 | my ($fn, $key, $value) = @_;
203 |
204 | $conns{$fn}{$key} += $value;
205 | }
206 |
207 | sub ConnDefined
208 | {
209 | my ($fn) = @_;
210 |
211 | return defined $conns{$fn};
212 | }
213 |
214 | sub ConnKeys
215 | {
216 | my ($fn) = @_;
217 |
218 | if (defined $conns{$fn})
219 | { return keys %{$conns{$fn}}; }
220 | else
221 | { return (); }
222 | }
223 |
224 | ########################################################################
225 | # Car Connection Registry
226 |
227 | my %car_conns; # Car connections (vkey -> fd#)
228 |
229 | sub CarConnect
230 | {
231 | my ($owner, $vehicleid, $fn) = @_;
232 |
233 | my $vkey = $owner . '/' . $vehicleid;
234 | $car_conns{$vkey} = $fn;
235 |
236 | my $clienttype = $conns{$fn}{'clienttype'};
237 | AE::log info => "#$fn $clienttype $vkey CarConnect";
238 | }
239 |
240 | sub CarDisconnect
241 | {
242 | my ($owner, $vehicleid, $fn) = @_;
243 |
244 | my $vkey = $owner . '/' . $vehicleid;
245 | my $clienttype = $conns{$fn}{'clienttype'};
246 | AE::log info => "#$fn $clienttype $vkey CarDisconnect";
247 |
248 | delete $car_conns{$vkey};
249 | }
250 |
251 | sub CarConnectionCount
252 | {
253 | my ($owner, $vehicleid) = @_;
254 |
255 | my $vkey = $owner . '/' . $vehicleid;
256 | return (defined $car_conns{$vkey})?1:0;
257 | }
258 |
259 | sub CarConnection
260 | {
261 | my ($owner, $vehicleid) = @_;
262 |
263 | my $vkey = $owner . '/' . $vehicleid;
264 | return undef if (!defined $car_conns{$vkey});
265 | return $car_conns{$vkey};
266 | }
267 |
268 | ########################################################################
269 | # App Connection Registry
270 |
271 | my %app_conns; # App connections (vkey{$fd#})
272 |
273 | sub AppConnect
274 | {
275 | my ($owner, $vehicleid, $fn) = @_;
276 |
277 | my $vkey = $owner . '/' . $vehicleid;
278 | $app_conns{$vkey}{$fn} = $fn;
279 |
280 | my $clienttype = $conns{$fn}{'clienttype'} || '-';
281 | AE::log info => "#$fn $clienttype $vkey AppConnect";
282 | }
283 |
284 | sub AppDisconnect
285 | {
286 | my ($owner, $vehicleid, $fn) = @_;
287 |
288 | my $vkey = $owner . '/' . $vehicleid;
289 | my $clienttype = $conns{$fn}{'clienttype'} || '-';
290 | AE::log info => "#$fn $clienttype $vkey AppDisconnect";
291 |
292 | delete $app_conns{$vkey}{$fn};
293 | }
294 |
295 | sub AppConnectionCount
296 | {
297 | my ($owner, $vehicleid) = @_;
298 |
299 | my $vkey = $owner . '/' . $vehicleid;
300 | return 0 if (!defined $app_conns{$vkey});
301 | return scalar keys %{$app_conns{$vkey}};
302 | }
303 |
304 | sub AppConnections
305 | {
306 | my ($owner, $vehicleid) = @_;
307 |
308 | my $vkey = $owner . '/' . $vehicleid;
309 | return () if (!defined $app_conns{$vkey});
310 |
311 | return sort keys %{$app_conns{$vkey}};
312 | }
313 |
314 | sub AppConnection
315 | {
316 | my ($owner, $vehicleid, $fn) = @_;
317 |
318 | my $vkey = $owner . '/' . $vehicleid;
319 | return undef if (!defined $app_conns{$vkey}{$fn});
320 | return $app_conns{$vkey}{$fn};
321 | }
322 |
323 |
324 | ########################################################################
325 | # Batch App Connection Registry
326 |
327 | my %batch_conns; # Batch connections (vkey{$fd#})
328 |
329 | sub BatchConnect
330 | {
331 | my ($owner, $vehicleid, $fn) = @_;
332 |
333 | my $vkey = $owner . '/' . $vehicleid;
334 | $batch_conns{$vkey}{$fn} = $fn;
335 |
336 | my $clienttype = $conns{$fn}{'clienttype'};
337 | AE::log info => "#$fn $clienttype $vkey BatchConnect";
338 | }
339 |
340 | sub BatchDisconnect
341 | {
342 | my ($owner, $vehicleid, $fn) = @_;
343 |
344 | my $vkey = $owner . '/' . $vehicleid;
345 | my $clienttype = $conns{$fn}{'clienttype'};
346 | AE::log info => "#$fn $clienttype $vkey BatchDisconnect";
347 |
348 | delete $batch_conns{$vkey}{$fn};
349 | }
350 |
351 | sub BatchConnectionCount
352 | {
353 | my ($owner, $vehicleid) = @_;
354 |
355 | my $vkey = $owner . '/' . $vehicleid;
356 | return 0 if (!defined $batch_conns{$vkey});
357 | return scalar keys %{$batch_conns{$vkey}};
358 | }
359 |
360 | sub BatchConnections
361 | {
362 | my ($owner, $vehicleid) = @_;
363 |
364 | my $vkey = $owner . '/' . $vehicleid;
365 | return () if (!defined $batch_conns{$vkey});
366 |
367 | return sort keys %{$batch_conns{$vkey}};
368 | }
369 |
370 | ########################################################################
371 | # Batch and App Connection Helpers
372 |
373 | sub ClientConnections
374 | {
375 | my ($owner, $vehicleid) = @_;
376 |
377 | my $vkey = $owner . '/' . $vehicleid;
378 |
379 | my @clist = ();
380 | push (@clist, sort keys %{$app_conns{$vkey}}) if (defined $app_conns{$vkey});
381 | push (@clist, sort keys %{$batch_conns{$vkey}}) if (defined $batch_conns{$vkey});
382 |
383 | return @clist;
384 | }
385 |
386 | ########################################################################
387 | # Connection Callback Functions
388 |
389 | sub ConnTransmit
390 | {
391 | my ($fn, $format, @data) = @_;
392 |
393 | return if (!defined $conns{$fn}{'callback_tx'});
394 |
395 | my $cb = $conns{$fn}{'callback_tx'};
396 | &$cb($fn, $format, @data);
397 | }
398 |
399 | sub CarTransmit
400 | {
401 | my ($owner, $vehicleid, $format, @data) = @_;
402 |
403 | my $vkey = $owner . '/' . $vehicleid;
404 | if (defined $car_conns{$vkey})
405 | {
406 | ConnTransmit($car_conns{$vkey}, $format, @data);
407 | }
408 | }
409 |
410 | sub ClientsTransmit
411 | {
412 | my ($owner, $vehicleid, $format, @data) = @_;
413 |
414 | CLIENT: foreach my $afn (ClientConnections($owner,$vehicleid))
415 | {
416 | next CLIENT if ($afn !~ /^\d+$/); # Ignore all the other types (like 'http:...')
417 | if (($conns{$afn}{'owner'} ne $owner) ||
418 | ($conns{$afn}{'vehicleid'} ne $vehicleid))
419 | {
420 | my $clienttype = $conns{$afn}{'clienttype'};
421 | my $vowner = $conns{$afn}{'owner'};
422 | my $vvehicleid = $conns{$afn}{'vehicleid'};
423 | AE::log error => "#$afn $clienttype $owner/$vehicleid ClientsTransmit mismatch $vowner/$vvehicleid";
424 | }
425 | else
426 | {
427 | my $clienttype = $conns{$afn}{'clienttype'};
428 | ConnTransmit($afn, $format, @data) if ($clienttype ne 'C');
429 | }
430 | }
431 | }
432 |
433 | sub ConnShutdown
434 | {
435 | my ($fn) = @_;
436 |
437 | return if (!defined $conns{$fn}{'callback_shutdown'});
438 |
439 | my $cb = $conns{$fn}{'callback_shutdown'};
440 | &$cb($fn);
441 | }
442 |
443 | 1;
444 |
--------------------------------------------------------------------------------
/v3/server/plugins/system/OVMS/Server/Plugin.pm:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 |
3 | ########################################################################
4 | # OVMS Server Plugin Manager
5 | #
6 | # This plugin provides the OVMS plugin manager, and is always
7 | # automatically loaded.
8 |
9 | package OVMS::Server::Plugin;
10 |
11 | use strict;
12 | use warnings;
13 | use Carp;
14 |
15 | use AnyEvent;
16 | use AnyEvent::Log;
17 | use Config::IniFiles;
18 | use OVMS::Server::Core;
19 |
20 | use Exporter qw(import);
21 |
22 | our @EXPORT = qw(PluginLoaded PluginCall
23 | RegisterFunction FunctionRegistered FunctionCall
24 | RegisterEvent EventRegistered EventCall);
25 |
26 | my $me; # Reference to our singleton object
27 | my %plugins; # Registered plugin modules
28 | my %functions; # Registered functions
29 | my %events; # Registered events
30 |
31 | use vars qw{
32 | };
33 |
34 | sub new
35 | {
36 | my $class = shift;
37 | $class = ref($class) || $class;
38 | my $self = {@_};
39 | bless( $self, $class );
40 |
41 | $me = $self;
42 | $self->init();
43 |
44 | return $self;
45 | }
46 |
47 | sub init
48 | {
49 | my ($self) = @_;
50 |
51 | EventCall('PluginsLoading');
52 |
53 | my $pluginlist = MyConfig()->val('plugins','load',"DbDBI");
54 | foreach my $plug (split /\n/,$pluginlist)
55 | {
56 | my $obj;
57 |
58 | AE::log info => "- - - loading plugin $plug...";
59 | eval
60 | {
61 | eval join('','use OVMS::Server::',$plug,';');
62 | AE::log error => "- - - error in $plug: $@" if ($@);
63 | eval join('','$obj = new OVMS::Server::',$plug,';');
64 | AE::log error => "- - - error in $plug: $@" if ($@);
65 | $plugins{$plug} = $obj;
66 | };
67 | if (!defined $plugins{$plug})
68 | {
69 | AE::log error => "- - - plugin $plug could not be installed";
70 | }
71 | }
72 |
73 | EventCall('PluginsLoaded');
74 | }
75 |
76 | sub PluginLoaded
77 | {
78 | my ($plugin) = @_;
79 |
80 | return defined $plugins{$plugin};
81 | }
82 |
83 | sub PluginCall
84 | {
85 | my ($plugin, $function, @params) = @_;
86 |
87 | if (defined $plugins{$plugin})
88 | {
89 | return $plugins{$plugin}->$function(@params);
90 | }
91 | else
92 | {
93 | return undef;
94 | }
95 | }
96 |
97 | sub RegisterFunction
98 | {
99 | my ($fn, $callback) = @_;
100 |
101 | my ($caller) = caller;
102 | ($caller) = caller(1) if ($caller eq 'OVMS::Server::Plugin');
103 |
104 | AE::log info => "- - - RegisterFunction $fn for $caller";
105 |
106 | $functions{$fn} = $callback;
107 | }
108 |
109 | sub FunctionRegistered
110 | {
111 | my ($fn) = @_;
112 |
113 | return defined $functions{$fn};
114 | }
115 |
116 | sub FunctionCall
117 | {
118 | my ($fn, @params) = @_;
119 |
120 | if (defined $functions{$fn})
121 | {
122 | my $cb = $functions{$fn};
123 | return $cb->(@params);
124 | }
125 | else
126 | {
127 | AE::log error => "- - - Function $fn does not exist";
128 | return undef;
129 | }
130 | }
131 |
132 | sub RegisterEvent
133 | {
134 | my ($event, $callback) = @_;
135 |
136 | my ($caller) = caller;
137 | ($caller) = caller(1) if ($caller eq 'OVMS::Server::Plugin');
138 |
139 | AE::log info => "- - - RegisterEvent $event for $caller";
140 |
141 | $events{$event}{$caller} = $callback;
142 | }
143 |
144 | sub EventRegistered
145 | {
146 | my ($event) = @_;
147 |
148 | return defined $events{$event};
149 | }
150 |
151 | sub EventCall
152 | {
153 | my ($event, @params) = @_;
154 |
155 | my @results = ();
156 | if (defined $events{$event})
157 | {
158 | foreach my $caller (sort keys %{$events{$event}})
159 | {
160 | my $cb = $events{$event}{$caller};
161 | push @results, $cb->(@params);
162 | }
163 | }
164 |
165 | return @results;
166 | }
167 |
168 | 1;
169 |
--------------------------------------------------------------------------------
/v3/server/plugins/system/OVMS/Server/Push.pm:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 |
3 | ########################################################################
4 | # OVMS Server PUSH notification plugin
5 | #
6 | # This plugin provides base support for Push Notification plugins.
7 | # It exposes an interface to send a push notification, and dispatches
8 | # those notifications to the appropriate handlers.
9 |
10 | package OVMS::Server::Push;
11 |
12 | use strict;
13 | use warnings;
14 | use Carp;
15 |
16 | use AnyEvent;
17 | use AnyEvent::Log;
18 | use OVMS::Server::Core;
19 | use OVMS::Server::Plugin;
20 |
21 | use Exporter qw(import);
22 |
23 | our @EXPORT = qw();
24 |
25 | # Push Notifications: Core notification module
26 |
27 | my $me; # Reference to our singleton object
28 | my $notifyhistory_tim; # Retain history push notifications (seconds)
29 |
30 | use vars qw{
31 | };
32 |
33 | sub new
34 | {
35 | my $class = shift;
36 | $class = ref($class) || $class;
37 | my $self = {@_};
38 | bless( $self, $class );
39 |
40 | $me = $self;
41 | $self->init();
42 |
43 | RegisterFunction('PushNotify',\&PushNotify);
44 |
45 | $notifyhistory_tim = MyConfig()->val('push','history',0);
46 |
47 | return $self;
48 | }
49 |
50 | sub init
51 | {
52 | my ($self) = @_;
53 | }
54 |
55 | my $notifyhistory_rec = 0;
56 | sub PushNotify
57 | {
58 | my ($owner, $vehicleid, $alerttype, $alertmsg) = @_;
59 |
60 | my $vkey = $owner . '/' . $vehicleid;
61 | my $timestamp = UTCTime();
62 |
63 | # VECE expansion...
64 | if ($alerttype eq 'E')
65 | {
66 | my ($vehicletype,$errorcode,$errordata) = split(/,/,$alertmsg);
67 | if (PluginLoaded('VECE'))
68 | {
69 | # VECE plugin is available
70 | $alertmsg = PluginCall('VECE','expansion',$vehicletype,$errorcode,$errordata);
71 | }
72 | else
73 | {
74 | $alertmsg = sprintf "Vehicle Alert Code: %s/%d (%08x)",$vehicletype,$errorcode,$errordata;
75 | }
76 | }
77 |
78 | # Log the alert
79 | if ($notifyhistory_tim > 0)
80 | {
81 | FunctionCall('DbSaveHistorical',UTCTime(),"*-Log-Notification",$notifyhistory_rec++,$owner,$vehicleid,"$alerttype,$alertmsg",UTCTime(time+$notifyhistory_tim));
82 | $notifyhistory_rec=0 if ($notifyhistory_rec>65535);
83 | }
84 |
85 | # Ignore DMC (debug) alerts
86 | return if (($alerttype eq 'E') && ($alertmsg =~ /\:\sDMC\:/));
87 |
88 | # Push the notifications out to subscribers...
89 | CANDIDATE: foreach my $row (FunctionCall('DbGetNotify',$owner,$vehicleid))
90 | {
91 | my %rec;
92 | $rec{'owner'} = $owner;
93 | $rec{'vehicleid'} = $vehicleid;
94 | $rec{'alerttype'} = $alerttype;
95 | $rec{'alertmsg'} = $alertmsg;
96 | $rec{'timestamp'} = $timestamp;
97 | $rec{'pushkeytype'} = $row->{'pushkeytype'};
98 | $rec{'pushkeyvalue'} = $row->{'pushkeyvalue'};
99 | $rec{'appid'} = $row->{'appid'};
100 |
101 | my $pushcall = 'PushNotify:' . $row->{'pushtype'};
102 | if (FunctionRegistered($pushcall))
103 | {
104 | AE::log info => "- - $vkey msg queued $rec{'pushkeytype'} notification for $rec{'pushkeytype'}:$rec{'appid'}";
105 | FunctionCall('PushNotify:'.$row->{'pushtype'}, \%rec);
106 | }
107 | else
108 | {
109 | AE::log info => "- - $vkey msg no notification handler registered for ".$rec{'pushkeytype'};
110 | }
111 | }
112 | EventCall('PushNow');
113 |
114 | return;
115 | }
116 |
117 | 1;
118 |
--------------------------------------------------------------------------------
/v3/server/plugins/system/OVMS/Server/PushAPNS.pm:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 |
3 | ########################################################################
4 | # OVMS Server PUSH to APNS notification plugin
5 | #
6 | # This plugin provides support for the APNS push notification system.
7 | # It requires plugin 'Push' be loaded in order to function.
8 |
9 | package OVMS::Server::PushAPNS;
10 |
11 | use strict;
12 | use warnings;
13 | use Carp;
14 |
15 | use EV;
16 | use AnyEvent;
17 | use AnyEvent::Log;
18 | use AnyEvent::IO;
19 | use AnyEvent::Socket;
20 | use AnyEvent::Handle;
21 | use AnyEvent::Util;
22 | use Net::APNS::Simple;
23 | use OVMS::Server::Core;
24 | use OVMS::Server::Plugin;
25 |
26 | use Exporter qw(import);
27 |
28 | our @EXPORT = qw();
29 |
30 | # Push Notifications: APNS notification module
31 |
32 | my $me; # Reference to our singleton object
33 | my @apns_queue_sandbox;
34 | my @apns_queue_production;
35 | my $apns_running=0;
36 | my $apns_interval;
37 | my $apnstim;
38 |
39 | if (!PluginLoaded('Push'))
40 | {
41 | AE::log error => "Error: Push MUST be loaded before this plugin";
42 | }
43 |
44 | use vars qw{
45 | };
46 |
47 | sub new
48 | {
49 | my $class = shift;
50 | $class = ref($class) || $class;
51 | my $self = {@_};
52 | bless( $self, $class );
53 |
54 | $me = $self;
55 | $self->init();
56 |
57 | RegisterFunction('PushNotify:apns',\&PushNotifyAPNS);
58 | RegisterEvent('PushNow',\&PushNow);
59 |
60 | $apns_interval = MyConfig()->val('apns','interval',10);
61 | $apnstim = AnyEvent->timer (after => $apns_interval, interval => $apns_interval, cb => \&apns_tim);
62 |
63 | return $self;
64 | }
65 |
66 | sub init
67 | {
68 | my ($self) = @_;
69 | }
70 |
71 | sub PushNotifyAPNS
72 | {
73 | my ($rec) = @_;
74 |
75 | if ($rec->{'pushkeyvalue'} eq '{length=32')
76 | {
77 | AE::log error => join(' ',
78 | '- -',
79 | $rec->{'owner'}.'/'.$rec->{'vehicleid'},
80 | 'msg skipped apns notification for',
81 | $rec->{'pushkeytype'}.':'.$rec->{'appid'},
82 | '- invalid token');
83 | return;
84 | }
85 |
86 | if ($rec->{'pushkeytype'} eq 'sandbox')
87 | { push @apns_queue_sandbox,$rec; }
88 | else
89 | { push @apns_queue_production,$rec; }
90 |
91 | return;
92 | }
93 |
94 | sub PushNow
95 | {
96 | &apns_tim() if (!$apns_running);
97 | }
98 |
99 | sub apns_tim
100 | {
101 | return if ($apns_running);
102 | return if ((scalar @apns_queue_sandbox == 0)&&(scalar @apns_queue_production == 0));
103 |
104 | $apns_running=1;
105 | SANDPROD: foreach my $sandbox (1,0)
106 | {
107 | my $apns;
108 | my @queue;
109 | if ($sandbox)
110 | {
111 | next SANDPROD if (scalar @apns_queue_sandbox == 0);
112 | $apns = Net::APNS::Simple->new(
113 | development => 1,
114 | cert_file => 'conf/ovms_apns_sandbox.pem',
115 | key_file => 'conf/ovms_apns_sandbox.pem',
116 | passwd_cb => sub { return '' },
117 | bundle_id => 'com.openvehicles.ovms'
118 | );
119 | @queue = @apns_queue_sandbox;
120 | @apns_queue_sandbox = ();
121 | }
122 | else
123 | {
124 | next SANDPROD if (scalar @apns_queue_production == 0);
125 | $apns = Net::APNS::Simple->new(
126 | cert_file => 'conf/ovms_apns_production.pem',
127 | key_file => 'conf/ovms_apns_production.pem',
128 | passwd_cb => sub { return '' },
129 | bundle_id => 'com.openvehicles.ovms'
130 | );
131 | @queue = @apns_queue_production;
132 | @apns_queue_production = ();
133 | }
134 |
135 | AE::log info => "- - - msg apns processing ".(scalar @queue)." queued notification(s) for ".($sandbox?'sandbox':'production');
136 | fork_call
137 | {
138 | my %results = ();
139 | foreach my $rec (@queue)
140 | {
141 | my $vehicleid = $rec->{'vehicleid'};
142 | my $alerttype = $rec->{'alerttype'};
143 | my $alertmsg = $rec->{'alertmsg'};
144 | my $pushkeyvalue = $rec->{'pushkeyvalue'};
145 | my $appid = $rec->{'appid'};
146 | $apns->prepare(
147 | $pushkeyvalue,
148 | {
149 | aps => {
150 | alert => $vehicleid . "\n" . $alertmsg,
151 | badge => 0,
152 | sound => "default",
153 | },
154 | },
155 | sub {
156 | my ($header, $content) = @_;
157 | my %result = @{$header};
158 | my $status = (defined $result{':status'})?$result{':status'}:'unknown';
159 | my $reason = (defined $result{'reason'})?$result{'reason'}:'';
160 | AE::log info => "- - $vehicleid msg apns message sent to $pushkeyvalue with $status:$reason" . ((defined $content)?" $content":'');
161 | }
162 | );
163 | }
164 | $apns->notify();
165 | }
166 | sub
167 | {
168 | # process child result
169 | my $result = shift;
170 | $apns_running=0;
171 | AE::log info => "- - - msg apns completed ".(scalar @queue)." queued notification(s) for ".($sandbox?'sandbox':'production');
172 | }
173 | }
174 | }
175 |
176 | 1;
177 |
--------------------------------------------------------------------------------
/v3/server/plugins/system/OVMS/Server/PushGCM.pm:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 |
3 | ########################################################################
4 | # OVMS Server PUSH to GCM (FCM) notification plugin
5 | #
6 | # This plugin provides support for the GCM/FCM push notification system.
7 | # It requires plugin 'Push' be loaded in order to function.
8 | #
9 | # Configuration:
10 | # [gcm]
11 | # api_key_file=
12 | # interval=10
13 | #
14 | # See /v3/README on how to get an API key file.
15 | #
16 |
17 |
18 | package OVMS::Server::PushGCM;
19 |
20 | use strict;
21 | use warnings;
22 | use Carp;
23 |
24 | use AnyEvent;
25 | use AnyEvent::Log;
26 | use AnyEvent::HTTP;
27 | use AnyEvent::Util;
28 | use URI::Escape;
29 | use OVMS::Server::Core;
30 | use OVMS::Server::Plugin;
31 | use JSON::XS;
32 | use Try::Tiny;
33 | use Digest::SHA qw(sha256 sha512);
34 | use WWW::FCM::HTTP::V1;
35 |
36 | use Exporter qw(import);
37 |
38 | our @EXPORT = qw();
39 |
40 | # Push Notifications: GCM notification module
41 |
42 | my $me; # Reference to our singleton object
43 | my @gcm_queue;
44 | my $gcm_running = 0;
45 | my $gcm_api_key_file;
46 | my $gcm_api_key_json;
47 | my $gcm_project_id;
48 | my $gcm_api_url;
49 | my $gcm_con;
50 | my $gcm_timer;
51 | my $gcm_timer_reinit;
52 |
53 | if (!PluginLoaded('Push'))
54 | {
55 | AE::log error => "Error: Push MUST be loaded before this plugin";
56 | }
57 |
58 | use vars qw{
59 | };
60 |
61 | sub new
62 | {
63 | my $class = shift;
64 | $class = ref($class) || $class;
65 | my $self = {@_};
66 | bless( $self, $class );
67 |
68 | $me = $self;
69 | $self->init();
70 |
71 | RegisterFunction('PushNotify:gcm',\&PushNotifyGCM);
72 | RegisterEvent('PushNow',\&PushNow);
73 |
74 | # Init FCM connection:
75 | $gcm_api_key_file = MyConfig()->val('gcm','api_key_file');
76 | if (!defined $gcm_api_key_file)
77 | {
78 | AE::log warn => "- - - GCM API key file not configured => GCM disabled";
79 | }
80 | else
81 | {
82 | if (open my $fh, '<', $gcm_api_key_file)
83 | {
84 | $gcm_api_key_json = do { local $/; <$fh> };
85 | close $fh;
86 | }
87 | else
88 | {
89 | AE::log warn => "- - - GCM API key file $gcm_api_key_file not found => GCM disabled";
90 | }
91 | }
92 | if (defined $gcm_api_key_json)
93 | {
94 | try
95 | {
96 | my $api_key = decode_json($gcm_api_key_json);
97 | $gcm_project_id = $api_key->{'project_id'};
98 | }
99 | catch
100 | {
101 | AE::log warn => "- - - GCM API key file $gcm_api_key_file invalid => GCM disabled";
102 | };
103 | }
104 | if (defined $gcm_project_id)
105 | {
106 | $gcm_api_url = "https://fcm.googleapis.com/v1/projects/$gcm_project_id/messages:send";
107 | &gcm_init_con();
108 | AE::log info => "- - - GCM initialized for project $gcm_project_id, API URL $gcm_api_url";
109 | }
110 |
111 | # Start ticker:
112 | my $gcm_interval = MyConfig()->val('gcm','interval',10);
113 | $gcm_timer = AnyEvent->timer (after => $gcm_interval, interval => $gcm_interval, cb => \&gcm_tim);
114 |
115 | # Start connection reinit ticker:
116 | my $gcm_interval_reinit = MyConfig()->val('gcm','interval_reinit',3600);
117 | $gcm_timer_reinit = AnyEvent->timer (after => $gcm_interval_reinit, interval => $gcm_interval_reinit, cb => \&gcm_init_con);
118 |
119 | return $self;
120 | }
121 |
122 | sub init
123 | {
124 | my ($self) = @_;
125 | }
126 |
127 | sub PushNotifyGCM
128 | {
129 | my ($rec) = @_;
130 | return if (!defined $gcm_con);
131 |
132 | push @gcm_queue,$rec;
133 |
134 | return;
135 | }
136 |
137 | sub PushNow
138 | {
139 | &gcm_tim();
140 | }
141 |
142 | sub gcm_init_con
143 | {
144 | if (defined $gcm_con)
145 | {
146 | undef $gcm_con;
147 | }
148 | $gcm_con = WWW::FCM::HTTP::V1->new(
149 | {
150 | api_url => $gcm_api_url,
151 | api_key_json => $gcm_api_key_json,
152 | });
153 | $gcm_running = 0;
154 | AE::log info => "- - - msg gcm connection initialized: " . $gcm_con;
155 | }
156 |
157 | sub gcm_tim
158 | {
159 | return if (!defined $gcm_con);
160 | return if ($gcm_running == 1);
161 | return if (scalar @gcm_queue == 0);
162 |
163 | # WWW::FCM::HTTP::V1->send() is synchronous, needs ~0.3 seconds per call.
164 | # Fork child process for asynchronous push message delivery:
165 |
166 | AE::log info => "- - - msg gcm processing queue: " . scalar @gcm_queue . " messages";
167 |
168 | if (my $rec = shift(@gcm_queue))
169 | {
170 | my $owner = $rec->{'owner'};
171 | my $vehicleid = $rec->{'vehicleid'};
172 | my $alerttype = $rec->{'alerttype'};
173 | my $alertmsg = $rec->{'alertmsg'};
174 | my $timestamp = $rec->{'timestamp'};
175 | my $pushkeyvalue = $rec->{'pushkeyvalue'};
176 | my $appid = $rec->{'appid'};
177 | AE::log info => "- - $vehicleid msg gcm '$alertmsg' => $pushkeyvalue";
178 |
179 | $gcm_running = 1;
180 |
181 | fork_call
182 | {
183 | # child process:
184 | my $res = $gcm_con->send(
185 | {
186 | message =>
187 | {
188 | token => $pushkeyvalue,
189 | data =>
190 | {
191 | type => $alerttype,
192 | time => $timestamp,
193 | title => $vehicleid,
194 | message => $alertmsg,
195 | },
196 | android =>
197 | {
198 | collapse_key => unpack("H*", sha256($alerttype . $timestamp . $vehicleid . $alertmsg)),
199 | },
200 | },
201 | });
202 | return $res;
203 | }
204 | sub
205 | {
206 | # process child result:
207 | my $res = shift;
208 | if (!defined $res)
209 | {
210 | # fork error, or FCM timeout / SSL error signaled by croak in WWW::FCM::HTTP::V1->send():
211 | AE::log error => "- - $vehicleid msg gcm fork_call error $!: $@";
212 | }
213 | elsif ($res->is_success)
214 | {
215 | AE::log info => "- - $vehicleid msg gcm message sent to $pushkeyvalue";
216 | }
217 | else
218 | {
219 | # probably an FCM failure response (JSON):
220 | try
221 | {
222 | AE::log debug => "- - $vehicleid msg gcm failure response: " . $res->{'content'};
223 | my $rescont = decode_json($res->{'content'});
224 | my $errcode = $rescont->{'error'}{'code'};
225 | my $errmsg = $rescont->{'error'}{'message'};
226 | AE::log error => "- - $vehicleid msg gcm error $errcode on $pushkeyvalue: $errmsg";
227 | # App instance unregistered from FCM?
228 | # see https://firebase.google.com/docs/reference/fcm/rest/v1/ErrorCode
229 | if ($errcode == 403 || $errcode == 404
230 | || $errmsg eq "The registration token is not a valid FCM registration token")
231 | {
232 | AE::log info => "- - $vehicleid msg gcm unregister $appid (error $errcode $errmsg)";
233 | FunctionCall('DbUnregisterPushNotify',$owner,$vehicleid,$appid);
234 | }
235 | }
236 | catch
237 | {
238 | # some other error:
239 | AE::log error => "- - $vehicleid msg gcm caught error: $_";
240 | };
241 | }
242 |
243 | $gcm_running = 0;
244 |
245 | # send next message if any:
246 | &gcm_tim();
247 | };
248 | } # if (my $rec = pop(@gcm_queue))
249 | }
250 |
251 | 1;
252 |
--------------------------------------------------------------------------------
/v3/server/plugins/system/OVMS/Server/PushMAIL.pm:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 |
3 | ########################################################################
4 | # OVMS Server PUSH to MAIL notificaiton plugin
5 | #
6 | # This plugin provides support for push notifications via EMAIL.
7 | # It requires plugin 'Push' be loaded in order to function.
8 |
9 | package OVMS::Server::PushMAIL;
10 |
11 | use strict;
12 | use warnings;
13 | use Carp;
14 |
15 | use AnyEvent;
16 | use AnyEvent::Log;
17 | use OVMS::Server::Core;
18 | use OVMS::Server::Plugin;
19 | use Email::MIME;
20 | use Email::Sender::Simple qw(sendmail);
21 |
22 | use Exporter qw(import);
23 |
24 | our @EXPORT = qw();
25 |
26 | # Push Notifications: MAIL notification module
27 |
28 | my $me; # Reference to our singleton object
29 | my @mail_queue;
30 | my $mail_sender;
31 | my $mail_interval;
32 | my $mail_running=0;
33 | my $mailtim;
34 |
35 | if (!PluginLoaded('Push'))
36 | {
37 | AE::log error => "Error: Push MUST be loaded before this plugin";
38 | }
39 |
40 | use vars qw{
41 | };
42 |
43 | sub new
44 | {
45 | my $class = shift;
46 | $class = ref($class) || $class;
47 | my $self = {@_};
48 | bless( $self, $class );
49 |
50 | $me = $self;
51 | $self->init();
52 |
53 | RegisterFunction('PushNotify:mail',\&PushNotifyMAIL);
54 | RegisterEvent('PushNow',\&PushNow);
55 |
56 | $mail_sender = MyConfig()->val('mail','sender','notifications@openvehicles.com');
57 | $mail_interval = MyConfig()->val('mail','interval',10);
58 | $mailtim = AnyEvent->timer (after => $mail_interval, interval => $mail_interval, cb => \&mail_tim);
59 |
60 | return $self;
61 | }
62 |
63 | sub init
64 | {
65 | my ($self) = @_;
66 | }
67 |
68 | sub PushNotifyMAIL
69 | {
70 | my ($rec) = @_;
71 |
72 | push @mail_queue,$rec;
73 |
74 | return;
75 | }
76 |
77 | sub PushNow
78 | {
79 | &mail_tim() if (!$mail_running);
80 | }
81 |
82 | sub mail_tim
83 | {
84 | return if ($mail_running);
85 | return if (scalar @mail_queue == 0);
86 |
87 | $mail_running=1;
88 | my @queue = @mail_queue;
89 | @mail_queue = ();
90 |
91 | foreach my $rec (@queue)
92 | {
93 | my $owner = $rec->{'owner'};
94 | my $vehicleid = $rec->{'vehicleid'};
95 | my $alerttype = $rec->{'alerttype'};
96 | my $alertmsg = $rec->{'alertmsg'};
97 | my $pushkeyvalue = $rec->{'pushkeyvalue'};
98 | if ($pushkeyvalue =~ /@/)
99 | {
100 | AE::log info => "- - $vehicleid msg mail '$alertmsg' => '$pushkeyvalue'";
101 | my $message = Email::MIME->create(
102 | header_str => [
103 | From => $mail_sender,
104 | To => $pushkeyvalue,
105 | Subject => "OVMS notification type $alerttype from $vehicleid",
106 | ],
107 | attributes => {
108 | encoding => 'quoted-printable',
109 | charset => 'ISO-8859-1',
110 | },
111 | body_str => $alertmsg,
112 | );
113 | sendmail($message);
114 | }
115 | }
116 | $mail_running=0;
117 | }
118 |
119 | 1;
120 |
--------------------------------------------------------------------------------
/v3/server/plugins/system/OVMS/Server/VECE.pm:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 |
3 | ########################################################################
4 | # OVMS Server VECE plugin
5 | #
6 | # This plugin provides support for expansion of vehicle error codes
7 | # into textual messages. It is used primarily by the push notification
8 | # system to expand vehicle error alert notifications. It's configuration
9 | # files are stored in 'vece/*.vece', usually with one file per vehicle
10 | # type.
11 |
12 | package OVMS::Server::VECE;
13 |
14 | use strict;
15 | use warnings;
16 | use Carp;
17 |
18 | use AnyEvent;
19 | use AnyEvent::Log;
20 | use Config::IniFiles;
21 | use OVMS::Server::Plugin;
22 |
23 | use Exporter qw(import);
24 |
25 | our @EXPORT = qw();
26 |
27 | # Vehicle Error Code Expansion configurations...
28 |
29 | my $me; # Reference to our singleton object
30 |
31 | use vars qw{
32 | $config
33 | };
34 |
35 | sub new
36 | {
37 | my $class = shift;
38 | $class = ref($class) || $class;
39 | my $self = {@_};
40 | bless( $self, $class );
41 |
42 | $me = $self;
43 | $self->init();
44 |
45 | return $self;
46 | }
47 |
48 | sub init
49 | {
50 | my ($self) = @_;
51 |
52 | $self->{'config'} = Config::IniFiles->new();
53 |
54 | foreach (sort glob 'vece/*.vece')
55 | {
56 | my $vecef = $_;
57 | AE::log info => "- - - loading $vecef";
58 | my $vece = Config::IniFiles->new(-file => $vecef);
59 | foreach ($vece->Sections())
60 | {
61 | my $s = $_;
62 | $self->{'config'}->AddSection($s);
63 | foreach ($vece->Parameters($s))
64 | {
65 | my $p = $_;
66 | $self->{'config'}->newval($s,$p,$vece->val($s,$p));
67 | }
68 | }
69 | }
70 | }
71 |
72 | sub expansion
73 | {
74 | my ($self,$vehicletype,$errorcode,$errordata) = @_;
75 |
76 | my $car = $vehicletype;
77 | while ($car ne '')
78 | {
79 | my $t = $self->{'config'}->val($car,$errorcode);
80 | if (defined $t)
81 | {
82 | my $text = sprintf $t,$errordata;
83 | return "Vehicle Alert #$errorcode: ".$text;
84 | }
85 | $car = substr($car,0,-1);
86 | }
87 |
88 | return sprintf "Vehicle Alert Code: %s/%d (%08x)",$vehicletype,$errorcode,$errordata;
89 | }
90 |
91 | 1;
92 |
--------------------------------------------------------------------------------
/v3/server/vece/o2.vece:
--------------------------------------------------------------------------------
1 | [O2]
2 |
--------------------------------------------------------------------------------
/v3/server/vece/tr.vece:
--------------------------------------------------------------------------------
1 | [TR]
2 | 0=Critical Brick Over Voltage fault
3 | 2=Critical Sheet UnderTemp fault
4 | 3=Maintenance Service Required
5 | 4=Battery too hot Vehicle shutting down
6 | 6=Battery Problem Service Required
7 | 7=Battery Bleed Scan Failure
8 | 8=Vehicle Idle for 1 Hour Shutting Down
9 | 10=Charging Problem
10 | 13=Critical Brick UnderVoltage fault
11 | 14=ESS Temperature Sensor Fault
12 | 16=BMB: Sheet alarm
13 | 19=Isolation resistance warning
14 | 23=SHUTTING DOWN PULL OVER SAFELY
15 | 25=Memory Stick transfer in progress (%d%%)
16 | 28=Transfer to memory stick has failed
17 | 29=Auxillary Power Supply inhibited
18 | 30=Airbag System Service required
19 | 32=SRS: Airbag must be serviced. Contact Tesla Service Center
20 | 43=VMS: APS Inhibited due to low brick voltage
21 | 50=BMB: No data fault
22 | 51=BSM: No data fault
23 | 52=CSB: No data fault
24 | 53=GPS: No data fault
25 | 54=HVAC: No data fault
26 | 55=IP: No data fault
27 | 56=PEM: No data fault, Power Reduced
28 | 57=SWP: No data fault
29 | 58=TPMS: No data fault
30 | 59=VMSIO: No data fault
31 | 60=DFC: No data fault
32 | 61=TCM: No data fault, Park Lock Problem
33 | 62=PM: No data fault
34 | 63=GS: No data fault - Power train
35 | 90=Transmission error Service required
36 | 91=Transmission error Service required
37 | 92=Transmission error Service required
38 | 93=Transmission error Service required
39 | 94=Park Lock Problem Vehicle May be Free-Rolling
40 | 95=Park failed to disengage. Service required.
41 | 97=Park Lock Problem Vehicle May be Free-Rolling
42 | 99=Park Lock Problem Vehicle May be Free-Rolling
43 | 101=Parking System Service Required
44 | 102=Parking System Service Required
45 | 103=Parking System Service Required
46 | 104=TCM: Lost DMC comms
47 | 105=Parking System Service Required
48 | 106=Parking System Service Required
49 | 107=TCM: CAN error
50 | 257=APS output 1 (main) reset
51 | 258=APS output 2 (PEM) reset
52 | 259=BSM: sheet alarm
53 | 260=BSM: Smoke detected
54 | 261=BSM: Smoke detector not reporting
55 | 263=BSM: HVAC cable fault
56 | 264=BSM: PEM cable fault
57 | 265=BSM: V_batt too low
58 | 266=BSM: V_batt too high
59 | 267=BSM: Positive/economizer stuck low
60 | 268=BSM: Positive/economizer stuck high
61 | 269=BSM: Negative contactor/economizer stuck low
62 | 270=BSM: Negative contactor/economizer stuck high
63 | 271=Battery Problem Vehicle shutting down
64 | 272=Powertrain Problem Restart When Safe
65 | 273=Powertrain Problem Restart When Safe
66 | 274=Powertrain Problem Restart When Safe
67 | 275=BSM: Precharge too fast at t1
68 | 276=Powertrain Problem Service Required
69 | 277=Powertrain Problem Service Required
70 | 278=BSM: V_ess too high at start of precharge
71 | 279=Powertrain Problem Service Required
72 | 280=BSM: Discharge enabled at start of precharge
73 | 281=BSM: Orientation fault
74 | 282=BSM: ESS OverTermp fault
75 | 283=BSM: Isolation fault
76 | 284=BSM: Voltage sensor error
77 | 285=BSM: Isolation fault before closing contactors
78 | 286=BSM: Isolation fault while contactors closed
79 | 287=BSM: Isolation resistance test error
80 | 288=BSM: V_ess too low during precharge, t2
81 | 398=Key must be on to select new tires
82 | 399=Cannot select new tires while moving.
83 | 400=Coast in a straight line at 20-60mph for 10 seconds
84 | 401=TPMS: Left front tire very soft
85 | 402=TPMS: Left front tire soft
86 | 403=TPMS: Right front tire very soft
87 | 404=TPMS: Right front tire soft
88 | 405=TPMS: Left rear tire very soft
89 | 406=TPMS: Left rear tire soft
90 | 407=TPMS: Right rear tire very soft
91 | 408=TPMS: Right rear tire soft
92 | 409=TPMS: Hardware Error
93 | 411=TPMS: Tire OverTerm warning
94 | 412=TPMS: Rapid tire pressure loss
95 | 413=TPMS: Check left front tire
96 | 414=TPMS: Check right front tire
97 | 415=TPMS: Check left back tire
98 | 416=TPMS: Check right back tire
99 | 420=SWP: APS off, but no pulse from BPS
100 | 428=SWP: Aux Battery Supply Low
101 | 429=SWP: Eeprom checksum error
102 | 430=SWP: CAN Rx OverFlow
103 | 525=ABS: Short circuit
104 | 526=SWP: Radio short circuit
105 | 527=Check right front high beam bulb
106 | 528=Check left front high beam bulb
107 | 529=Check right front low beam bulb
108 | 530=Check left front low beam bulb
109 | 532=Left window switch open/short circuit
110 | 533=Right window switch open/short circuit
111 | 536=Check left back turn signal bulb
112 | 537=Check left front turn signal bulb
113 | 538=Check right back turn signal bulb
114 | 539=Check right front turn signal bulb
115 | 600=APS: Cooling issue; power limiting in effect
116 | 601=ESS: MaxT brick too hot; power limiting in effect
117 | 602=SHUTDOWN IMMINENT Power limited
118 | 603=BSM: Isolation resistance fault; power limiting in effect
119 | 604=ESS: Low state of chrge; power limiting in effect / Battery Low Power Reduced
120 | 605=Standard range SOC floor reached. Stop & Charge.
121 | 606=ESS: Range Mode Remaining charge uncertain
122 | 607=ESS: MaxT brick extremely hot; power limiting in effect
123 | 608=ESS: Almost empty. Car stops in 3 miles
124 | 609=ESS: BSM/CSB/BMB no data fault; power limited
125 | 610=ESS: Almost empty. Car stops in 2 miles
126 | 611=ESS: Almost empty. Car stops in 1 mile
127 | 612=Multiple Temp Sensor Faults; Vehicle entering limp mode
128 | 613=VMS: Use MC120 to avoid trickle charge
129 | 700=Brake fluid low
130 | 701=ABS: Service required
131 | 702=ABS: Service required
132 | 703=ABS: Service required
133 | 720=900A discharge limit exceeded
134 | 721=863A discharge limit exceeded
135 | 722=805A discharge limit exceeded
136 | 723=300A discharge limit exceeded
137 | 724=200A discharge limit exceeded
138 | 725=190A charge limit exceeded
139 | 726=75A charge limit exceeded
140 | 727=60A charge limit exceeded
141 | 728=Overtemp event during charge or regen
142 | 729=Undertemp event during charge or regen
143 | 730=750A discharge warning
144 | 731=450A discharge warning; power limited
145 | 732=300A discharge warning; power limited
146 | 733=200A discharge warning; power limited
147 | 734=75A charge warning
148 | 735=60A charge warning
149 | 880=While starting, keep foot off accelerator pedal
150 | 881=While starting, foot brake must be pressed
151 | 882=Before starting, close charge port door
152 | 883=Before starting, exit Tow Mode
153 | 884=Battery empty Can't start
154 | 885=BMB No Data fault Can't start
155 | 886=Battery cold Can't start
156 | 887=Can't start Version number mismatch.
157 | 888=VMS/PEM key mismatch / Disarm vehicle with key fob before starting
158 | 889=Service Required. Charing restricted to storage level.
159 | 901=DMC HW: PhaseA OverCurrent Fault
160 | 902=DMC HW: PhaseB OverCurrent Fault
161 | 903=DMC HW: PhaseC OverCurrent Fault
162 | 904=DMC HW: Battery Over Voltage Fault
163 | 905=DMC HW: PhaseA Low Side Desat Fault
164 | 906=DMC HW: PhaseA High Side Desat Fault
165 | 907=DMC HW: PhaseA Bias Under Voltage Fault
166 | 908=DMC HW: PhaseA Bus Over voltage fault
167 | 909=DMC HW: PhaseB Low Side Desat Fault
168 | 910=DMC HW: PhaseB High Side Desat Fault
169 | 911=DMC HW: PhaseB Bias UnderVoltage fault
170 | 912=DMC HW: PhaseB Bus OverVoltage fault
171 | 913=DMC HW: PhaseC Low Side Desat fault
172 | 914=DMC HW: PhaseC High Side Desat fault
173 | 915=DMC HW: PhaseC Bias UnderVoltage fault
174 | 916=DMC HW: PhaseC Bus OverVoltage fault
175 | 917=DMC HW: APS UnderVoltage fault
176 | 918=DMC HW: Motor OverTemp fault
177 | 919=DMC HW: Pedal Monitor fault
178 | 920=DMC HW: Line OverCurrent fault
179 | 921=DMC HW: PhaseA OverTemp fault
180 | 922=DMC HW: PhaseB OverTemp fault
181 | 923=DMC HW: PhaseC OverTerm fault
182 | 924=DMC HW: ESS Cable Interlock fault
183 | 925=DMC HW: Common Mode Sense fault
184 | 925=DMC HW: Common Mode Sense fault
185 | 926=DMC HW: PDP Interrupt fault
186 | 927=DMC HW: PhaseA OverCurrent fault
187 | 928=DMC HW: PhaseB OverCurrent fault
188 | 929=DMC HW: PhaseC OverCurrent fault
189 | 930=DMC FW: PhaseA OverCurrent Peak fault
190 | 931=DMC FW: PhaseB OverCurrent Peak fault
191 | 932=DMC FW: PhaseC OverCurrent Peak fault
192 | 933=DMC FW: Line OverCurrent fault
193 | 934=DMC FW: Line OverCurrent Peak Fault
194 | 935=DMC FW: Line OverVoltage fault
195 | 936=DMC FW: Line OverVoltage Peak fault
196 | 937=DMC FW: Line UnderVoltage fault / Charge Problem Extension Cord Detected
197 | 938=DMC FW: Battery OverVoltage fault
198 | 939=DMC FW: Battery UnderVoltage fault
199 | 940=DMC FW: Motor OverSpeed fault
200 | 941=DMC FW: Motor Sensor1 OverTemp fault
201 | 942=DMC FW: Motor Sensor2 OverTerm fault
202 | 943=DMC FW: Motor Sensor1 UnderTemp fault
203 | 944=DMC FW: Motor Sensor2 UnderTerm fault
204 | 945=DMC FW: PhaseA OverTemp fault / System too hot - Vehicle shutting down
205 | 946=DMC FW: PhaseA Temp Diff fault
206 | 947=DMC FW: PhaseB OverTemp fault
207 | 948=DMC FW: PhaseB Temp Diff fault
208 | 949=DMC FW: PhaseC OverTemp fault
209 | 950=DMC FW: PhaseC Temp Diff fault
210 | 951=DMC FW: Ambient OverTemp fault
211 | 952=DMC FW: Amvient UnderTemp fault
212 | 953=DMC FW: Line OverFrequency fault
213 | 954=DMC FW: Line UnderFrequency fault
214 | 955=DMC FW: Line Sync Loss fault
215 | 956=DMC FW: HCS Faulted
216 | 957=DMC FW: PEM Vbat diff from BSM Vbat fault
217 | 958=DMC FW: HCS Invalid Pilot Signal fault
218 | 959=DMC FW: Line Current or Voltage Offset Too Large fault
219 | 960=DMC FW: VBrickMax versus VBattery Error fault
220 | 961=DMC FW: VBrickMax or VBrickLimit is invalid
221 | 962=DMC FW: Line Current Error fault
222 | 963=DMC FW: Line Current Not Equal Request fault
223 | 964=DMC FW: Pilot Signal Present in Drive Mode fault
224 | 965=DMC FW: Gear Selector Comms fault
225 | 966=DMC FW: Invalid Shift Request Fault
226 | 967=DMC FW: Press brake before shifting fault
227 | 968=DMC FW: Lost VMS Comms fault
228 | 969=DMC FW: Lost DFC Comms fault
229 | 970=DMC FW: Lost ABS Comms (and TC) fault
230 | 971=DMC FW: CAN Comms Error fault
231 | 972=DMC FW: Accelerator Error fault
232 | 973=DMC FW: Motor Encoder Error fault
233 | 974=DMC FW: Pole Current Error fault
234 | 975=DMC FW: ABS Error fault
235 | 976=DMC FW: Invalid State fault
236 | 977=DMC FW: Moving during charge fault
237 | 978=DMC FW: Invalid State request fault
238 | 979=DMC FW: Invalid Mode request fault
239 | 980=DMC FW: Invalid Charge Request fault
240 | 981=DMC FW: Bad State Transition fault
241 | 982=DMC FW: Mode Trans Condition fault
242 | 983=DMC FW: Memory Error fault
243 | 984=DMC FW: Bad Argument fault
244 | 985=DMC FW: Interrupt Time too Long fault
245 | 986=DMC FW: MotorTemp Sensor1 fault
246 | 987=DMC FW: MotorTemp Sensor2 fault
247 | 988=DMC FW: MotorTemp Sensors differ fault
248 | 989=DMC FW: VBrickMin Not Increasing
249 | 990=DMC FW: Voltage Discharge Time fault
250 | 991=DMC FW: 5V Power
251 | 992=DMC FW: Lost TCM comms
252 | 993=DMC FW: PCS Current Offset Too Large fault
253 | 994=DMC FW: Watchdog timer reset fault
254 | 995=DMC FW: TCM Refusal fault
255 | 996=DMC FW: Unsafe gear transition fault
256 | 997=DMC FW: CAN Rx
257 | 998=DMC FW: CAN Tx
258 | 999=DMC FW: CAN Overrun
259 | 1061=DMC FW: Line OverCurrent warning
260 | 1063=DMC FW: Line OverVoltage warning
261 | 1065=DMC FW: Line UnderVoltage warning
262 | 1066=DMC FW: Battery OverVoltage warning. Torque Limited.
263 | 1067=DMC FW: Battery UnderVoltage warning. Torque Limited.
264 | 1069=DMC FW: Motor sensor 1 OverTemp warning. Torque limited
265 | 1070=DMC FW: Motor sensor 2 OverTemp warning. Torque limited
266 | 1073=DMC FW: PhaseA OverTemp warning. Torque limited
267 | 1074=DMC FW: PhaseA Temp Diff warning
268 | 1075=DMC FW: PhaseB OverTemp warning. Torque limited
269 | 1076=DMC FW: PhaseB Temp Diff warning
270 | 1077=DMC FW: PhaseC OverTemp warning. Torque limited
271 | 1078=DMC FW: PhaseC Temp Diff warning
272 | 1081=DMC FW: Line OverFrequency warning
273 | 1082=DMC FW: Line UnderFrequency warning
274 | 1084=DMC FW: HCS Faulted warning, External Charger Problem
275 | 1085=DMC FW: PEM Vbat different from BSM Vbat warning
276 | 1086=DMC FW: HCS Invalid Pilot Signal warning
277 | 1087=DMC FW: Line Current or Voltage Offset Too Large warning
278 | 1090=DMC FW: Line Current Error warning
279 | 1091=DMC FW: Line Current Not Equal Request warning
280 | 1092=DMC FW: Pilot Signal Present in Drive Mode warning
281 | 1093=DMC FW: Shifter error warning
282 | 1094=DMC FW: Invalid Shift Request warning
283 | 1095=DMC FW: Press brake before shifting warning
284 | 1096=DMC FW: Lost VMS comms warning
285 | 1097=DMC FW: Lost DFC comms warning
286 | 1098=DMC FW: Lost ABS Comms (and TC) warning
287 | 1099=DMC FW: CAN comms error warning
288 | 1102=DMC FW: Pole Current Error warning
289 | 1103=DMC FW: ABS Error warning
290 | 1110=DMC FW: Mode Trans Condition warning
291 | 1112=DMC FW: Bad Argument warning
292 | 1119=DMC FW: 5V Power
293 | 1120=DMC FW: Lost TCM comms warning
294 | 1123=DMC FW: TCM Refusal Warning
295 | 1124=DMC FW: Unsafe gear transition warning
296 | 1125=DMC FW: CAN Rx
297 | 1131=DMC FW: Lost comms With Shifter
298 | 1136=DMC FW: APS Undervoltage
299 | 1138=DMC FW: Voltage on Charge Port Line2 during drive
300 | 1142=DMC FW: DMC FAULT Shifter Lights Don't Match Gear
301 | 1144=DMC: Powertrain Problem Service Required
302 | 1146=DMC: Motor Fan Problem
303 | 1152=DMC: WARNING Charge Thermal Limit
304 | 1153=DMC: Wear Factor Warning
305 | 1160=Drivers door ajar
306 | 1161=Passenger door ajar
307 | 1162=Trunk ajar
308 | 1165=Key in Key Switch Door Ajar
309 | 1166=Release Parking Brake
310 | 1167=Headlights still on
311 | 1169=Charge port open
312 | 1170=Tow Mode is not available while car is on
313 | 1174=Fasten Seatbelt
314 | 1207=DFC: Pole Fan Falled
315 | 1208=DFC: Pole Fan OverCurrent
316 | 1209=DFC: Pole Fan 12V OverVoltage
317 | 1210=DFC: Pole Fan 12V UnderVoltage
318 | 1211=DFC: Pole Fan Control OverTemp
319 | 1212=DFC: Pole Fan UnderCurrent
320 | 1400=HVAC: Coolant Pump UnderCurrent
321 | 1412=HVAC: Low Pressure
322 | 1416=HVAC: Load Shed Timeout
323 | 1417=HVAC: Compressor Low Speed
324 | 1418=HVAC: Compressor Stalled
325 | 1419=HVAC: Compressor Statup has Failed
326 | 1423=HVAC: Compressor Overheated
327 | 1424=HVAC: Compressor Controller Overheated
328 | 1425=HVAC: Low current Pump failure
329 | 1426=HVAC: High current Pump failure
330 | 1426=HVAC: High current pump failure
331 | 1427=HVAC: Compressor Speed Limited Range
332 | 1428=HVAC: Consolidated Compressor Fault(s)
333 | 1440=HVAC: Coolant Pump UnderCurrent
334 | 1447=HVAC: Outlet OverTemp
335 | 1452=HVAC: Low pressure
336 | 1453=HVAC: Refrigerant High Pressure / Coolant System Problem Service Required
337 | 1455=HVAC: Pressure Sensor Fail
338 | 1458=HVAC: Compressor 7D Timeout
339 | 1463=HVAC: Compressor OverTemp
340 | 1464=HVAC: Compressor Controller Electric Fault
341 | 1465=HVAC: Low current pump fault
342 | 1466=HVAC: High current pump fault
343 | 1467=HVAC: Compressor 410 Invalid
344 | 1468=HVAC: Compressor 7D Invalid
345 | 1469=HVAC: Compressor comms timeout
346 | 1470=HVAC: Temp Sensor Fault
347 | 1471=HVAC: Compressor DTC comms timeout
348 | 1493=ESS: Extremely Low. Begin Charging ASAP.
349 | 1494=VMS: Odometer value suspect
350 | 1495=ESS: Smoke sensed. Power limited; charging prevented.
351 | 1496=VMS: Key state mismatch
352 | 1522=SWP: BPS Active
353 | 1533=VMS: VMS firmware version doesn't match car-wide release
354 | 1534=VMS: VMSIO firmware version doesn't match car-wide release
355 | 1535=VMS: linux kernel version doesn't match car-wide release
356 | 1536=VMS: BSM firmware version doesn't match car-wide release
357 | 1537=VMS: BMB firmware version doesn't match car-wide release
358 | 1538=VMS: CSB firmware version doesn't match car-wide release
359 | 1539=VMS: SWP firmware version doesn't match car-wide release
360 | 1540=VMS: VDS firmware version doesn't match car-wide release
361 | 1541=VMS: DMC firmware version doesn't match car-wide release
362 | 1542=VMS: PM firmware version doesn't match car-wide release
363 | 1543=VMS: DFC firmware version doesn't match car-wide release
364 | 1544=VMS: TCM firmware version doesn't match car-wide release
365 | 1545=VMS: HVAC firmware version doesn't match car-wide release
366 | 1546=VMS: IP firmware version doesn't match car-wide release
367 | 1547=VMS: CPLD firmware version doesn't match car-wide release
368 | 1548=VMS: Not receiving VMSIO version number
369 | 1549=VMS: Not receiving Linux kernel version number
370 | 1550=VMS: Not receiving DMC version number
371 | 1551=VMS: Not receiving CSB version number
372 | 1552=VMS: Not receiving BMB version number
373 | 1553=VMS: Not receiving SWP version number
374 | 1554=VMS: Not receiving VDS version number
375 | 1555=VMS: Not receiving DMC version number
376 | 1556=VMS: Not receiving PM version number
377 | 1557=VMS: Not receiving DFC version number
378 | 1558=VMS: Not receiving TCM version number
379 | 1559=VMS: Not receiving HVAC version number
380 | 1560=VMS: Not receiving IP version number
381 | 1561=VMS: Not receiving CPLD version number
382 | 1641=PM: Current in Neutral fault
383 | 1657=PM: Current in Neutral warning
384 | 1658=PM: Cruise Not off warning
385 | 1660=PM: Invalid Shift Request warning
386 | 1661=PM: Shifter Error warning
387 | 1664=PM: Lost VMS comms warning
388 | 1665=PM: Lost ABS comms warning
389 | 1666=PM: Lost DMC comms warning
390 | 1667=PM: ABS Error warning
391 | 1668=PM: PCS Error warning
392 | 1669=PM: CAN Error warning
393 | 1670=PM: Accelerator Error warning
394 | 1901=Not Available
395 | 2005=No response to request to stop charging
396 | 2017=No response to request to change charge mode
397 | 2031=No response to request to save charging time
398 | 2033=No response to request to save charge timing
399 | 2035=No response to request to save current limit
400 | 2037=No response to request to save cost/kWh
401 | 2070=No response to request to get keyfob function
402 | 2078=There was a problem processing your request / No response to request to enter Tow Mode
403 | 2096=No response to Pin submission
404 | 3011=SHFT: Warning DRIVE Switch Problem
405 | 3021=SHFT: Neutral switch Stuck Down
406 | 3022=SHFT: DRIVE switch Stuck Down
407 |
--------------------------------------------------------------------------------
/v3/server/vece/va.vece:
--------------------------------------------------------------------------------
1 | [VA]
2 |
--------------------------------------------------------------------------------
/v3/shell/ovms_shell.conf.default:
--------------------------------------------------------------------------------
1 | [db]
2 | path=DBI:mysql:database=openvehicles;host=127.0.0.1
3 | user=
4 | pass=
5 |
--------------------------------------------------------------------------------
/v3/shell/ovms_shell.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 |
3 | use EV;
4 | use AnyEvent;
5 | use AnyEvent::Handle;
6 | use AnyEvent::Socket;
7 | use AnyEvent::ReadLine::Gnu;
8 | use Digest::MD5;
9 | use Digest::HMAC;
10 | use Crypt::RC4::XS;
11 | use MIME::Base64;
12 | use DBI;
13 | use Config::IniFiles;
14 |
15 | # Command line interface
16 | select STDOUT; $|=1;
17 | my $server = $ARGV[0]; $server="127.0.0.1" if (!defined $server);
18 | my $port = $ARGV[1]; $port=6867 if (!defined $port);
19 |
20 | # Configuration
21 | my $config = Config::IniFiles->new(-file => 'ovms_shell.conf') if (-e 'ovms_shell.conf');
22 |
23 | # Database
24 | our $db = DBI->connect($config->val('db','path'),$config->val('db','user'),$config->val('db','pass')) if (defined $config);
25 | $db->{mysql_auto_reconnect} = 1 if (defined $db);
26 |
27 | # Globals
28 | my $b64tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
29 | my $connvid;
30 | my $connpass;
31 | my $conn;
32 | my $connected = 0;
33 | my $client_token;
34 | my $txcipher;
35 | my $rxcipher;
36 | my $rl;
37 |
38 | sub conn_line
39 | {
40 | my ($hdl, $data) = @_;
41 |
42 | my $decoded = $rxcipher->RC4(decode_base64($data));
43 | $decoded =~ tr /\r/\n/;
44 |
45 | $rl->hide();
46 | if ($decoded =~ /^MP-0 Z(\d+)/)
47 | {
48 | $rl->print("Server reports $1 app(s) connected (including this shell)\n\n");
49 | }
50 | elsif ($decoded =~ /^MP-0 T(\d+)/)
51 | {
52 | if ($1 == 0)
53 | {
54 | if ($connected==1)
55 | {
56 | $rl->print("Vehicle is online live\n\n");
57 | $connected=2;
58 | }
59 | }
60 | else
61 | { $rl->print("Vehicle data is currently $1 second(s) delayed\n\n"); }
62 | }
63 | elsif ($decoded =~ /^MP-0 F(.+)/)
64 | {
65 | my ($version,$vin,$netsq,$netsq1,$type,$provider) = split /,/,$1;
66 | $rl->print("Vehicle Firmware Message:
67 | Version: $version
68 | Type: $type
69 | VIN: $vin
70 | Signal: $provider ($netsq)\n\n");
71 | }
72 | elsif ($decoded =~ /^MP-0 f(.+)/)
73 | {
74 | $rl->print("Server Firmware Message:
75 | Version: $1\n\n");
76 | }
77 | elsif ($decoded =~ /^MP-0 W(.+)/)
78 | {
79 | my ($fr_p,$fr_t,$rr_p,$rr_t,$fl_p,$fl_t,$rl_p,$rl_t) = split /,/,$1;
80 | $rl->print("Vehicle TPMS Message:
81 | FL: $fl_p PSI $fl_t C
82 | FR: $fr_p PSI $fr_t C
83 | RL: $rl_p PSI $rl_t C
84 | RR: $rr_p PSI $rr_t C\n\n");
85 | }
86 | elsif ($decoded =~ /^MP-0 L(.+)/)
87 | {
88 | my ($lat,$lon,$dir,$alt,$gpslock,$speed,$drivemode,$batp,$bateu,$bater) = split /,/,$1;
89 | $rl->print("Vehicle Location Message:
90 | Position: $lat,$lon (lock: $gpslock)
91 | Direction: $dir
92 | Altitude: $alt
93 | Speed: $speed
94 | DriveMode: $drivemode
95 | Battery: power $batp used $bateu recd $bater\n\n");
96 | }
97 | elsif ($decoded =~ /^MP-0 S(.+)/)
98 | {
99 | my ($soc,$units,$cv,$cc,$cs,$cm,$ri,$re,$climit,$ctime,$b4,$chkwh,$chss,$chs,$chm,
100 | $ctm,$cts,$stale,$cac,$cdf,$cle,$cls,$cooling,$cooltb,$cooltl,$chargest,$minsr,$minssoc,
101 | $bmr,$chargetype,$batv,$soh) = split /,/,$1;
102 | $rl->print("Vehicle Status Message:
103 | Battery: soc $soc cac $cac soh $soh voltage $batv range-full $bmr$units
104 | Range: ideal $ri$units estimated $re$units
105 | Charge: state $cs mode $cm ($cv V $cc A, limit $climit A) type $chargetype
106 | state $chs mode $chm substate $chss
107 | time $ctime duration $cd ($chkwh kWh) to-full $cdf to-set $cle$units/$cls%%
108 | timer $ctm (start $cts)
109 | Cooling: $cooling (limit temp $cooltb time $cooltl) estimated $chargest $minsr$units $minssoc%%\n\n");
110 | }
111 | elsif ($decoded =~ /^MP-0 D(.+)/)
112 | {
113 | my ($doors1,$doors2,$locked,$ti,$tm,$tb,$trip,$odometer,$speed,$parktime,$te,
114 | $doors3,$tstale,$estale,$b12v,$doors4,$b12r,$doors5,$tc,$b12c,$tcab) = split /,/,$1;
115 | $rl->print("Vehicle Environment Message
116 | Temps: inverter $ti motor $tm battery $tb environment $te charger $tc cabin $tcab (stale $tstale)
117 | Locked: $locked
118 | Trip/Odo: trip $trip odometer $odometer speed $speed park-time $parktime
119 | 12vBatt: level $b12v current $b12c (ref $b12r)
120 | Doors: doors1 $doors1 doors2 $doors2 doors3 $doors3 doors4 $doors4 doors5 $doors5\n\n");
121 | }
122 | elsif ($decoded =~ /^MP-0 c7,0,(.+)$/s)
123 | {
124 | $rl->print("Vehicle Response:\n " . join("\n ",split(/\n/,$1)) . "\n");
125 | }
126 | else
127 | {
128 | $rl->print("<$decoded\n");
129 | }
130 | $rl->show();
131 | $hdl->push_read(line => \&conn_line);
132 | }
133 |
134 | sub conn_login
135 | {
136 | my ($hdl, $data) = @_;
137 | $rl->hide();
138 | $rl->print("<$data\n");
139 |
140 | my ($welcome,$crypt,$server_token,$server_digest) = split /\s+/,$data;
141 | my $d_server_digest = decode_base64($server_digest);
142 | my $client_hmac = Digest::HMAC->new($connpass, "Digest::MD5");
143 | $client_hmac->add($server_token);
144 |
145 | if ($client_hmac->digest() ne $d_server_digest)
146 | {
147 | $rl->print("Server digest is invalid (incorrect vehicleid/password?)\n");
148 | undef $conn;
149 | $rl->show();
150 | return;
151 | }
152 |
153 | $client_hmac = Digest::HMAC->new($connpass, "Digest::MD5");
154 | $client_hmac->add($server_token);
155 | $client_hmac->add($client_token);
156 | my $client_key = $client_hmac->digest;
157 | $txcipher = Crypt::RC4::XS->new($client_key);
158 | $txcipher->RC4(chr(0) x 1024); # Prime the cipher
159 | $rxcipher = Crypt::RC4::XS->new($client_key);
160 | $rxcipher->RC4(chr(0) x 1024); # Prime the cipher
161 | $rl->print("Connected and logged in to $connvid\n");
162 | $rl->show();
163 | $connected = 1;
164 | $conn->push_read(line => \&conn_line);
165 | };
166 |
167 | sub command
168 | {
169 | my ($line) = @_;
170 | $line =~ s/^\s+//g;
171 | $line =~ s/\s+$//g;
172 |
173 | if ($line =~ /^open\s+(\S+)(\s+(\S+))?$/)
174 | {
175 | if (defined $conn)
176 | {
177 | $rl->print("Closing connection to $connvid\n");
178 | undef $conn;
179 | }
180 | $rl->print("Connecting to $1...\n");
181 | $connvid = $1;
182 | $connpass = $3;
183 | if ((!defined $connpass)&&(defined $db))
184 | {
185 | my $sth = $db->prepare('SELECT * FROM ovms_cars WHERE vehicleid=?');
186 | $sth->execute($connvid);
187 | my $row = $sth->fetchrow_hashref();
188 | if (defined $row)
189 | {
190 | $connpass = $row->{'carpass'};
191 | $rl->print("(vehicle password retrieved from server database)\n");
192 | }
193 | }
194 | $conn = new AnyEvent::Handle
195 | connect => [$server,$port],
196 | on_connect => sub
197 | {
198 | my ($hdl) = @_;
199 | $rl->hide();
200 | $rl->print("Connected to server\n");
201 | foreach (0 .. 21) { $client_token .= substr($b64tab,rand(64),1); }
202 | my $client_hmac = Digest::HMAC->new($connpass, "Digest::MD5");
203 | $client_hmac->add($client_token);
204 | my $client_digest = $client_hmac->b64digest();
205 | $rl->print(">MP-A 0 $client_token $client_digest $connvid\n");
206 | $hdl->push_write("MP-A 0 $client_token $client_digest $connvid\r\n");
207 | $hdl->push_read(line => \&conn_login);
208 | $rl->show();
209 | },
210 | on_connect_error => sub
211 | {
212 | $rl->hide();
213 | $rl->print("Failed to connect to server ($!)\n");
214 | undef $conn;
215 | $connected =0;
216 | $rl->show();
217 | },
218 | on_error => sub
219 | {
220 | $rl->hide();
221 | $rl->print("Disconnected from server ($!)\n");
222 | undef $conn;
223 | $connected = 0;
224 | $rl->show();
225 | },
226 | }
227 | elsif ($line =~ /^close$/)
228 | {
229 | if (defined $conn)
230 | {
231 | $rl->print("Closing connection to $connvid\n");
232 | undef $conn;
233 | $connected = 0;
234 | }
235 | }
236 | elsif ($line =~ /^search\s(.+)$/)
237 | {
238 | if (!defined $db)
239 | {
240 | $rl->print("No database connection defined\n");
241 | }
242 | else
243 | {
244 | my $search = '%'.$1.'%';
245 | my $sth = $db->prepare('SELECT * FROM ovms_owners,ovms_cars,ovms_carmessages '
246 | . "WHERE ovms_owners.owner=ovms_cars.owner AND ovms_carmessages.vehicleid=ovms_cars.vehicleid "
247 | . "AND ovms_carmessages.m_code='F' "
248 | . "AND ((ovms_cars.vehicleid LIKE ?) OR (name LIKE ?) OR (mail LIKE ?) OR (vehiclename LIKE ?) OR (m_msg LIKE ?)) "
249 | . "AND ovms_owners.deleted=0 AND ovms_cars.deleted=0");
250 | $sth->execute($search,$search,$search,$search,$search);
251 | my $first = 0;
252 | while (my $row = $sth->fetchrow_hashref())
253 | {
254 | if ($first==0)
255 | {
256 | $first=1;
257 | printf "%-32.32s %-32.32s %-19.19s %s\n",'VehicleID','Owner Name','Last Connected','Owner eMail';
258 | }
259 | printf "%-32.32s %-32.32s %-19.19s %s\n",$row->{'owner'}.'/'.$row->{'vehicleid'},$row->{'name'},$row->{'m_msgtime'},$row->{'mail'};
260 | }
261 | }
262 | }
263 | else
264 | {
265 | # Send the line to the car, as a command...
266 | if (!$connected)
267 | {
268 | $rl->print("Not connected, so can't command vehicle\n");
269 | }
270 | else
271 | {
272 | $conn->push_write(encode_base64($txcipher->RC4("MP-0 C7,".$line),'')."\r\n");
273 | $rl->print("...\n");
274 | }
275 | }
276 | }
277 |
278 | print "Welcome to the OVMS shell\n";
279 | print "Server is $server:$port (can be specified on the command line)\n";
280 | print "Enter 'open []' to connect to a vehicle, or 'close' to disconnect\n";
281 | print "\n";
282 | $rl = new AnyEvent::ReadLine::Gnu
283 | prompt => "ovms> ",
284 | on_line => sub
285 | {
286 | # called for each line entered by the user
287 | my ($line) = @_;
288 | if (!defined $line)
289 | {
290 | $rl->print("All done\n");
291 | EV::unloop();
292 | }
293 | else
294 | {
295 | $rl->hide();
296 | &command($line);
297 | $rl->show();
298 | }
299 | };
300 |
301 | # Main event loop...
302 | EV::loop();
303 | exit 0;
304 |
--------------------------------------------------------------------------------
/v3/support/ovms.logrotate:
--------------------------------------------------------------------------------
1 | /var/log/ovms_server.log
2 | /var/log/ovms_democar.log
3 | {
4 | compress
5 | copytruncate
6 | daily
7 | dateext
8 | dateyesterday
9 | maxage 90
10 | missingok
11 | }
12 |
--------------------------------------------------------------------------------
/v3/support/ovms_democar.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=OVMS DEMO car
3 | After=network.target
4 |
5 | [Service]
6 | Type=simple
7 | WorkingDirectory=/home/openvehicles/v3/v3/demos
8 | ExecStart=/bin/sh -c 'exec ./ovms_democar.pl >>/var/log/ovms_democar.log 2>&1'
9 | Restart=always
10 | RestartSec=5
11 |
12 | [Install]
13 | WantedBy=multi-user.target
14 |
--------------------------------------------------------------------------------
/v3/support/ovms_server.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=OVMS v3 server
3 | After=network.target
4 |
5 | [Service]
6 | Type=simple
7 | WorkingDirectory=/home/openvehicles/v3/v3/server
8 | ExecStart=/bin/sh -c 'exec ./ovms_server.pl >>/var/log/ovms_server.log 2>&1'
9 | Restart=always
10 | RestartSec=5
11 | LimitNOFILE=infinity
12 |
13 | [Install]
14 | WantedBy=multi-user.target
15 |
--------------------------------------------------------------------------------
/v3/utils/ap_check.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 |
3 | use Digest::MD5;
4 | use Digest::HMAC;
5 | use Crypt::RC4::XS;
6 | use MIME::Base64;
7 |
8 | my $b64tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
9 |
10 | select STDOUT; $|=1;
11 |
12 | print "ICCID: ";
13 | my $iccid = <>; chop $iccid;
14 | print "Server Token: ";
15 | my $server_token = <>; chop $server_token;
16 | print "Server Digest: ";
17 | my $server_digest_b64 = <>; chop $server_digest_b64;
18 | print "AP Record: ";
19 | my $ap_record = <>; chop $ap_record; $ap_record = decode_base64($ap_record);
20 | print "\n";
21 |
22 | my $shared_secret = $iccid;
23 |
24 | my $server_hmac = Digest::HMAC->new($shared_secret, "Digest::MD5");
25 | $server_hmac->add($server_token);
26 | my $server_digest = $server_hmac->b64digest();
27 |
28 | if ($server_digest ne $server_digest_b64)
29 | {
30 | print "ERROR: Server digest does not authenticate ($server_digest != $server_digest_b64)\n";
31 | exit(1);
32 | }
33 |
34 | print "Server Digest: AUTHENTICATED\n";
35 |
36 | my $server_key= decode_base64($server_digest);
37 |
38 | my $rxcipher = Crypt::RC4::XS->new($server_key);
39 | $rxcipher->RC4(chr(0) x 1024); # Prime the cipher
40 |
41 | my $decrypted = $rxcipher->RC4($ap_record);
42 | print "AP Record: ",$decrypted,"\n";
43 |
--------------------------------------------------------------------------------
/v3/utils/ap_generate.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 |
3 | use Digest::MD5;
4 | use Digest::HMAC;
5 | use Crypt::RC4::XS;
6 | use MIME::Base64;
7 |
8 | my $b64tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
9 |
10 | select STDOUT; $|=1;
11 |
12 | print "VIN: ";
13 | my $vin = <>; chop $vin;
14 | print "ICCID: ";
15 | my $iccid = <>; chop $iccid;
16 | my @apparams;
17 | my $k=0;
18 | printf "#%-4s: ",$k;
19 | while (<>)
20 | {
21 | chop;
22 | last if (/^$/);
23 | push @apparams,$_;
24 | $k++; printf "#%-4s: ",$k;
25 | }
26 | my $apn = join ' ',@apparams;
27 | my $shared_secret = $iccid;
28 |
29 | print "\n";
30 | print "Generating Auto-Provisioning record:\n";
31 | print "VIN: ",$vin,"\n";
32 | print "ICCID: ",$iccid,"\n";
33 | print "Params: ",join(' ',@apparams),"\n";
34 | print "\n";
35 | rand;
36 | my $server_token;
37 | foreach (0 .. 21)
38 | { $server_token .= substr($b64tab,rand(64),1); }
39 |
40 | print "Server Token: ",$server_token,"\n";
41 |
42 | my $server_hmac = Digest::HMAC->new($shared_secret, "Digest::MD5");
43 | $server_hmac->add($server_token);
44 | my $server_digest = $server_hmac->b64digest();
45 | print "Server Digest: ",$server_digest,"\n";
46 |
47 | my $server_key= decode_base64($server_digest);
48 |
49 | my $txcipher = Crypt::RC4::XS->new($server_key);
50 | $txcipher->RC4(chr(0) x 1024); # Prime the cipher
51 |
52 | my $encrypted = encode_base64($txcipher->RC4($apn),'');
53 | print "AP Record: ",$encrypted,"\n";
54 |
--------------------------------------------------------------------------------
/v3/utils/client_car.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 |
3 | use Digest::MD5;
4 | use Digest::HMAC;
5 | use Crypt::RC4::XS;
6 | use MIME::Base64;
7 | use IO::Socket::INET;
8 |
9 | my $b64tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
10 |
11 | my $shared_secret = "NETPASS";
12 | print STDERR "Shared Secret is: ",$shared_secret,"\n";
13 |
14 | #####
15 | ##### CONNECT
16 | #####
17 |
18 | my $sock = IO::Socket::INET->new(PeerAddr => '127.0.0.1',
19 | PeerPort => '6867',
20 | Proto => 'tcp');
21 |
22 | #####
23 | ##### CLIENT
24 | #####
25 |
26 | print STDERR "I am the client\n";
27 |
28 | rand;
29 | my $client_token;
30 | foreach (0 .. 21)
31 | { $client_token .= substr($b64tab,rand(64),1); }
32 | print STDERR " Client token is $client_token\n";
33 |
34 | my $client_hmac = Digest::HMAC->new($shared_secret, "Digest::MD5");
35 | $client_hmac->add($client_token);
36 | my $client_digest = $client_hmac->b64digest();
37 | print STDERR " Client digest is $client_digest\n";
38 |
39 | print $sock "MP-C 0 $client_token $client_digest TESTCAR\r\n";
40 |
41 | my $line = <$sock>;
42 | chop $line;
43 | chop $line;
44 | print STDERR " Received $line from server\n";
45 | my ($welcome,$crypt,$server_token,$server_digest) = split /\s+/,$line;
46 |
47 | print STDERR " Received $server_token $server_digest from server\n";
48 |
49 | my $d_server_digest = decode_base64($server_digest);
50 | my $client_hmac = Digest::HMAC->new($shared_secret, "Digest::MD5");
51 | $client_hmac->add($server_token);
52 | if ($client_hmac->digest() ne $d_server_digest)
53 | {
54 | print STDERR " Client detects server digest is invalid - aborting\n";
55 | exit(1);
56 | }
57 | print STDERR " Client verification of server digest is ok\n";
58 |
59 | $client_hmac = Digest::HMAC->new($shared_secret, "Digest::MD5");
60 | $client_hmac->add($server_token);
61 | $client_hmac->add($client_token);
62 | my $client_key = $client_hmac->digest;
63 | print STDERR " Client version of shared key is: ",encode_base64($client_key,''),"\n";
64 |
65 | my $txcipher = Crypt::RC4::XS->new($client_key);
66 | $txcipher->RC4(chr(0) x 1024); # Prime the cipher
67 | my $rxcipher = Crypt::RC4::XS->new($client_key);
68 | $rxcipher->RC4(chr(0) x 1024); # Prime the cipher
69 |
70 | my $encrypted = encode_base64($txcipher->RC4("MP-0 A"),'');
71 | print STDERR " Sending message $encrypted\n";
72 | print $sock "$encrypted\r\n";
73 |
74 | $line = <$sock>;
75 | chop $line;
76 | chop $line;
77 | print STDERR " Received $line from server\n";
78 | print STDERR " Server message decodes to: ",$rxcipher->RC4(decode_base64($line)),"\n";
79 |
80 | $encrypted = encode_base64($txcipher->RC4("MP-0 S80,K,220,70,charging,standard,280,270"),'');
81 | print STDERR " Sending message $encrypted\n";
82 | print $sock "$encrypted\r\n";
83 |
84 | $encrypted = encode_base64($txcipher->RC4("MP-0 L22.274165,114.185715"),'');
85 | print STDERR " Sending message $encrypted\n";
86 | print $sock "$encrypted\r\n";
87 |
88 | #$encrypted = encode_base64($txcipher->RC4("MP-0 PMSonny: This is a 3rd test of the vehicle transmission system"),'');
89 | #print STDERR " Sending message $encrypted\n";
90 | #print $sock "$encrypted\r\n";
91 |
92 | while(<$sock>)
93 | {
94 | chop; chop;
95 | print STDERR " Received $_ from server\n";
96 | my $line = $rxcipher->RC4(decode_base64($_));
97 | print STDERR " Server message decodes to: ",$line,"\n";
98 | if ($line =~ /^MP-0 A/)
99 | {
100 | $encrypted = encode_base64($txcipher->RC4("MP-0 a"),'');
101 | print STDERR " Sending message $encrypted\n";
102 | print $sock "$encrypted\r\n";
103 | }
104 | }
105 |
--------------------------------------------------------------------------------