├── .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 | --------------------------------------------------------------------------------