├── .gitignore ├── LICENSE.md ├── README.md ├── composer.json ├── demo.php └── src └── HSPDev └── HuaweiApi ├── CustomHttpClient.php └── Router.php /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io 2 | 3 | ### Composer ### 4 | composer.phar 5 | vendor/ 6 | 7 | # Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file 8 | # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file 9 | # composer.lock 10 | 11 | 12 | ### OSX ### 13 | .DS_Store 14 | .AppleDouble 15 | .LSOverride 16 | 17 | # Icon must end with two \r 18 | Icon 19 | 20 | 21 | # Thumbnails 22 | ._* 23 | 24 | # Files that might appear in the root of a volume 25 | .DocumentRevisions-V100 26 | .fseventsd 27 | .Spotlight-V100 28 | .TemporaryItems 29 | .Trashes 30 | .VolumeIcon.icns 31 | 32 | # Directories potentially created on remote AFP share 33 | .AppleDB 34 | .AppleDesktop 35 | Network Trash Folder 36 | Temporary Items 37 | .apdisk 38 | 39 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) [2015] [Henrik Sylvester Pedersen] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Huawei E5180 API 2 | 3 | This project will let you interface with your Huawei E5180 Cube router easily. 4 | This router is deployed by 3 DK, as "home routers" for their 4G connections. 5 | You can get it for free if you order a 100GB or above package from 3. 6 | Link where I bought mine: [https://www.3.dk/mobiler-tablets/modems-routere/huawei/huawei-e5180-cube](https://www.3.dk/mobiler-tablets/modems-routere/huawei/huawei-e5180-cube) 7 | 8 | No advertisement or affiliation or anything, I wouldn't even really recommend the router. It got great speeds, but the WiFi is not the greatest, 9 | and I've seen it overload multiple times when you have lots of clients on your (W)LAN. The 4G link holds, but the LAN/WiFi/Router portion is really bad, so I personally use it with a D-link DIR-810l as router, with it's WAN port connected to the Huawei routers single LAN port, so it can focus on just delivering 4G Internet. 10 | 11 | ## Features 12 | 13 | - Send SMS through the mobile network 14 | - Receive SMS (you need to pool for this periodically) 15 | - Delete SMS (to avoid filling it up) 16 | - Query router status (lots of info) 17 | - Query traffic statistics (decent amount of info) 18 | - Get the current PLMN (which network you are on) 19 | - Get craddle status (hardware info, battery and such if yours have one) 20 | - Get WLAN clients 21 | - Check the device for notifications (Including SMS and updates) 22 | - Query LED status (the big blue one on top) 23 | - Turn the LED on or off. 24 | 25 | What could you use this for? Do you want a free inbound SMS gateway for your home? Now you got it, if you have this router. I know 3.dk currently charges me 0.20 DKK for outbound SMS, as of 19-06-2015, but be sure to check with your own provider before sending SMS. I could also easily imaging your provider being mad at you if you suddenly used this as a commercial gateway, sending and receiving thousands of SMS messages. 26 | 27 | If you like me, live in the country without good internet through wire, and want a 4G router like this, you could also make your own interface to keep your bandwidth bills in check. 28 | 29 | Also, the blue LED is quite fun to play with. You can tap the top, to turn it on and off, so I already have plans for letting me know if I have new emails by turning the LED blue. 30 | 31 | ## Documentation 32 | 33 | How do I use this library? 34 | 35 | Simply include the composer autoloader into your project after installation, as you are used to, and proceed to make a Router object. Set the address of your router and login. Now every other function listed here SHOULD work, at least on E5180. I can't talk about any other routers compliance. 36 | 37 | ```php 38 | require_once 'vendor/autoload.php'; 39 | 40 | // The router class is the main entry point for interaction. 41 | $router = new HSPDev\HuaweiApi\Router; 42 | 43 | // If specified without http or https, assumes http:// 44 | $router->setAddress('192.168.8.1'); 45 | 46 | // Username and password. 47 | // Username is always admin as far as I can tell. 48 | $router->login('admin', 'your-password'); 49 | 50 | var_dump($router->getLedStatus()); 51 | ``` 52 | 53 | This will get the current status off the blue LED on top, either true or false, for on/off. If this seems to work, try the following line instead: 54 | 55 | ```php 56 | var_dump($router->setLedOn(!$router->getLedStatus())); 57 | ``` 58 | 59 | Every time you run the script now, it should turn the LED on or off, depending on it's current state. 60 | 61 | **Now let's try something else.** 62 | 63 | ```php 64 | var_dump($router->getNetwork()); 65 | ``` 66 | 67 | Which should return something like the following, which shows that I'm currently on the "3 DK"" network. You can look up PLMN lists to get the numeric codes. 68 | 69 | ```php 70 | object(SimpleXMLElement)#8 (5) { 71 | ["State"]=> 72 | string(1) "0" 73 | ["FullName"]=> 74 | string(4) "3 DK" 75 | ["ShortName"]=> 76 | string(4) "3 DK" 77 | ["Numeric"]=> 78 | string(5) "23806" 79 | ["Rat"]=> 80 | string(1) "2" 81 | } 82 | ``` 83 | 84 | **What about some SMS?** 85 | 86 | ```php 87 | var_dump($router->getInbox()); 88 | ``` 89 | 90 | In my case it returned this, meaning I have no new messages. 91 | 92 | ```php 93 | object(SimpleXMLElement)#6 (2) { 94 | ["Count"]=> 95 | string(1) "0" 96 | ["Messages"]=> 97 | object(SimpleXMLElement)#4 (0) { 98 | } 99 | } 100 | ``` 101 | 102 | That can't be true. Let's send some to our router. You can probably find the phone number for your router on your bills, sometimes in the web interface or maybe by simple logging into the web interface and sending yourself a message. After sending my router a SMS I got this result instead: 103 | 104 | ```php 105 | object(SimpleXMLElement)#6 (2) { 106 | ["Count"]=> 107 | string(1) "1" 108 | ["Messages"]=> 109 | object(SimpleXMLElement)#4 (1) { 110 | ["Message"]=> 111 | object(SimpleXMLElement)#8 (9) { 112 | ["Smstat"]=> 113 | string(1) "0" 114 | ["Index"]=> 115 | string(5) "40000" 116 | ["Phone"]=> 117 | string(11) "(my phone number)" 118 | ["Content"]=> 119 | string(3) "Lol" 120 | ["Date"]=> 121 | string(19) "2015-06-19 15:27:15" 122 | ["Sca"]=> 123 | object(SimpleXMLElement)#9 (0) { 124 | } 125 | ["SaveType"]=> 126 | string(1) "4" 127 | ["Priority"]=> 128 | string(1) "0" 129 | ["SmsType"]=> 130 | string(1) "1" 131 | } 132 | } 133 | } 134 | ``` 135 | 136 | Have a look inside the Router.php class to find out what methods you can use, it's very well documented, but I will throw a list here anyway. 137 | 138 | - login($username, $password) username is almost always "admin". "password" is the one for the web interface. 139 | - getStatus() gives info about the routers status. 140 | - getTrafficStats() gives traffic info 141 | - getMonthStats() does the same for the current month (if you have setup limits) 142 | - getNetwork() gives info about the current network. Can't find the bars anywhere tho. 143 | - getCraddleStatus() lots of more info. I suspect you can get battery status here, if your device has one. 144 | - getSmsCount() DOES NOT RETURN AN integer, but also an XML object. 145 | - getWlanClients() gets a list of WlanClients, if they have IP 0.0.0.0 they are disconnected. 146 | - getNotifications() does what it says. 147 | - setLedOn(boolean $on) call with "true" to turn on, and "false" for off. 148 | - getLedStatus() true/false for LED status. 149 | - isLoggedIn() true/false to check if logged in. 150 | - getInbox($page = 1, $count = 20, $unreadPreferred = false) defaults are fine for most tinkering. page/count for pagination. 151 | - deleteSms($index) provide with SMS index for deleting. Returns true if not found also. 152 | - sendSms($receiver, $message) Pretty self explanatory. Might return true and not send anyway. There is an API to query for send status, but I didn't worry about it. 153 | 154 | I don't promise that these will work like advertised or at all, just have fun. It should get you started. 155 | 156 | ## Huawei Router API Error codes 157 | 158 | Sometimes if you are experimenting with the Huawei API and trying to talk with it, you will get a random error code back. This sucks, to say it politely, as there is absolutely no information on what is going wrong. Therefore, I've gotten hold of this list of error codes for the Huawei API, which I know is true for the E5180 and probably other devices too. So if you just googled "Huawei Router API Error" or something like that, congratulations, today is your lucky day. 159 | Please note that not all codes are in here, but most of them are. 160 | 161 | ```php 162 | ERROR_BUSY = 100004 163 | ERROR_CHECK_SIM_CARD_CAN_UNUSEABLE = 101004 164 | ERROR_CHECK_SIM_CARD_PIN_LOCK = 101002 165 | ERROR_CHECK_SIM_CARD_PUN_LOCK = 101003 166 | ERROR_COMPRESS_LOG_FILE_FAILED = 103102 167 | ERROR_CRADLE_CODING_FAILED = 118005 168 | ERROR_CRADLE_GET_CRURRENT_CONNECTED_USER_IP_FAILED = 118001 169 | ERROR_CRADLE_GET_CRURRENT_CONNECTED_USER_MAC_FAILED = 118002 170 | ERROR_CRADLE_GET_WAN_INFORMATION_FAILED = 118004 171 | ERROR_CRADLE_SET_MAC_FAILED = 118003 172 | ERROR_CRADLE_UPDATE_PROFILE_FAILED = 118006 173 | ERROR_DEFAULT = -1 174 | ERROR_DEVICE_AT_EXECUTE_FAILED = 103001 175 | ERROR_DEVICE_COMPRESS_LOG_FILE_FAILED = 103015 176 | ERROR_DEVICE_GET_API_VERSION_FAILED = 103006 177 | ERROR_DEVICE_GET_AUTORUN_VERSION_FAILED = 103005 178 | ERROR_DEVICE_GET_LOG_INFORMATON_LEVEL_FAILED = 103014 179 | ERROR_DEVICE_GET_PC_AISSST_INFORMATION_FAILED = 103012 180 | ERROR_DEVICE_GET_PRODUCT_INFORMATON_FAILED = 103007 181 | ERROR_DEVICE_NOT_SUPPORT_REMOTE_OPERATE = 103010 182 | ERROR_DEVICE_PIN_MODIFFY_FAILED = 103003 183 | ERROR_DEVICE_PIN_VALIDATE_FAILED = 103002 184 | ERROR_DEVICE_PUK_DEAD_LOCK = 103011 185 | ERROR_DEVICE_PUK_MODIFFY_FAILED = 103004 186 | ERROR_DEVICE_RESTORE_FILE_DECRYPT_FAILED = 103016 187 | ERROR_DEVICE_RESTORE_FILE_FAILED = 103018 188 | ERROR_DEVICE_RESTORE_FILE_VERSION_MATCH_FAILED = 103017 189 | ERROR_DEVICE_SET_LOG_INFORMATON_LEVEL_FAILED = 103013 190 | ERROR_DEVICE_SET_TIME_FAILED = 103101 191 | ERROR_DEVICE_SIM_CARD_BUSY = 103008 192 | ERROR_DEVICE_SIM_LOCK_INPUT_ERROR = 103009 193 | ERROR_DHCP_ERROR = 104001 194 | ERROR_DIALUP_ADD_PRORILE_ERROR = 107724 195 | ERROR_DIALUP_DIALUP_MANAGMENT_PARSE_ERROR = 107722 196 | ERROR_DIALUP_GET_AUTO_APN_MATCH_ERROR = 107728 197 | ERROR_DIALUP_GET_CONNECT_FILE_ERROR = 107720 198 | ERROR_DIALUP_GET_PRORILE_LIST_ERROR = 107727 199 | ERROR_DIALUP_MODIFY_PRORILE_ERROR = 107725 200 | ERROR_DIALUP_SET_AUTO_APN_MATCH_ERROR = 107729 201 | ERROR_DIALUP_SET_CONNECT_FILE_ERROR = 107721 202 | ERROR_DIALUP_SET_DEFAULT_PRORILE_ERROR = 107726 203 | ERROR_DISABLE_AUTO_PIN_FAILED = 101008 204 | ERROR_DISABLE_PIN_FAILED = 101006 205 | ERROR_ENABLE_AUTO_PIN_FAILED = 101009 206 | ERROR_ENABLE_PIN_FAILED = 101005 207 | ERROR_FIRST_SEND = 1 208 | ERROR_FORMAT_ERROR = 100005 209 | ERROR_GET_CONFIG_FILE_ERROR = 100008 210 | ERROR_GET_CONNECT_STATUS_FAILED = 102004 211 | ERROR_GET_NET_TYPE_FAILED = 102001 212 | ERROR_GET_ROAM_STATUS_FAILED = 102003 213 | ERROR_GET_SERVICE_STATUS_FAILED = 102002 214 | ERROR_LANGUAGE_GET_FAILED = 109001 215 | ERROR_LANGUAGE_SET_FAILED = 109002 216 | ERROR_LOGIN_ALREADY_LOGINED = 108003 217 | ERROR_LOGIN_MODIFY_PASSWORD_FAILED = 108004 218 | ERROR_LOGIN_NO_EXIST_USER = 108001 219 | ERROR_LOGIN_PASSWORD_ERROR = 108002 220 | ERROR_LOGIN_TOO_MANY_TIMES = 108007 221 | ERROR_LOGIN_TOO_MANY_USERS_LOGINED = 108005 222 | ERROR_LOGIN_USERNAME_OR_PASSWORD_ERROR = 108006 223 | ERROR_NET_CURRENT_NET_MODE_NOT_SUPPORT = 112007 224 | ERROR_NET_MEMORY_ALLOC_FAILED = 112009 225 | ERROR_NET_NET_CONNECTED_ORDER_NOT_MATCH = 112006 226 | ERROR_NET_REGISTER_NET_FAILED = 112005 227 | ERROR_NET_SIM_CARD_NOT_READY_STATUS = 112008 228 | ERROR_NOT_SUPPORT = 100002 229 | ERROR_NO_DEVICE = -2 230 | ERROR_NO_RIGHT = 100003 231 | ERROR_NO_SIM_CARD_OR_INVALID_SIM_CARD = 101001 232 | ERROR_ONLINE_UPDATE_ALREADY_BOOTED = 110002 233 | ERROR_ONLINE_UPDATE_CANCEL_DOWNLODING = 110007 234 | ERROR_ONLINE_UPDATE_CONNECT_ERROR = 110009 235 | ERROR_ONLINE_UPDATE_GET_DEVICE_INFORMATION_FAILED = 110003 236 | ERROR_ONLINE_UPDATE_GET_LOCAL_GROUP_COMMPONENT_INFORMATION_FAILED = 110004 237 | ERROR_ONLINE_UPDATE_INVALID_URL_LIST = 110021 238 | ERROR_ONLINE_UPDATE_LOW_BATTERY = 110024 239 | ERROR_ONLINE_UPDATE_NEED_RECONNECT_SERVER = 110006 240 | ERROR_ONLINE_UPDATE_NOT_BOOT = 110023 241 | ERROR_ONLINE_UPDATE_NOT_FIND_FILE_ON_SERVER = 110005 242 | ERROR_ONLINE_UPDATE_NOT_SUPPORT_URL_LIST = 110022 243 | ERROR_ONLINE_UPDATE_SAME_FILE_LIST = 110008 244 | ERROR_ONLINE_UPDATE_SERVER_NOT_ACCESSED = 110001 245 | ERROR_PARAMETER_ERROR = 100006 246 | ERROR_PB_CALL_SYSTEM_FUCNTION_ERROR = 115003 247 | ERROR_PB_LOCAL_TELEPHONE_FULL_ERROR = 115199 248 | ERROR_PB_NULL_ARGUMENT_OR_ILLEGAL_ARGUMENT = 115001 249 | ERROR_PB_OVERTIME = 115002 250 | ERROR_PB_READ_FILE_ERROR = 115005 251 | ERROR_PB_WRITE_FILE_ERROR = 115004 252 | ERROR_SAFE_ERROR = 106001 253 | ERROR_SAVE_CONFIG_FILE_ERROR = 100007 254 | ERROR_SD_DIRECTORY_EXIST = 114002 255 | ERROR_SD_FILE_EXIST = 114001 256 | ERROR_SD_FILE_IS_UPLOADING = 114007 257 | ERROR_SD_FILE_NAME_TOO_LONG = 114005 258 | ERROR_SD_FILE_OR_DIRECTORY_NOT_EXIST = 114004 259 | ERROR_SD_IS_OPERTED_BY_OTHER_USER = 114004 260 | ERROR_SD_NO_RIGHT = 114006 261 | ERROR_SET_NET_MODE_AND_BAND_FAILED = 112003 262 | ERROR_SET_NET_MODE_AND_BAND_WHEN_DAILUP_FAILED = 112001 263 | ERROR_SET_NET_SEARCH_MODE_FAILED = 112004 264 | ERROR_SET_NET_SEARCH_MODE_WHEN_DAILUP_FAILED = 112002 265 | ERROR_SMS_DELETE_SMS_FAILED = 113036 266 | ERROR_SMS_LOCAL_SPACE_NOT_ENOUGH = 113053 267 | ERROR_SMS_NULL_ARGUMENT_OR_ILLEGAL_ARGUMENT = 113017 268 | ERROR_SMS_OVERTIME = 113018 269 | ERROR_SMS_QUERY_SMS_INDEX_LIST_ERROR = 113020 270 | ERROR_SMS_SAVE_CONFIG_FILE_FAILED = 113047 271 | ERROR_SMS_SET_SMS_CENTER_NUMBER_FAILED = 113031 272 | ERROR_SMS_TELEPHONE_NUMBER_TOO_LONG = 113054 273 | ERROR_STK_CALL_SYSTEM_FUCNTION_ERROR = 116003 274 | ERROR_STK_NULL_ARGUMENT_OR_ILLEGAL_ARGUMENT = 116001 275 | ERROR_STK_OVERTIME = 116002 276 | ERROR_STK_READ_FILE_ERROR = 116005 277 | ERROR_STK_WRITE_FILE_ERROR = 116004 278 | ERROR_UNKNOWN = 100001 279 | ERROR_UNLOCK_PIN_FAILED = 101007 280 | ERROR_USSD_AT_SEND_FAILED = 111018 281 | ERROR_USSD_CODING_ERROR = 111017 282 | ERROR_USSD_EMPTY_COMMAND = 111016 283 | ERROR_USSD_ERROR = 111001 284 | ERROR_USSD_FUCNTION_RETURN_ERROR = 111012 285 | ERROR_USSD_IN_USSD_SESSION = 111013 286 | ERROR_USSD_NET_NOT_SUPPORT_USSD = 111022 287 | ERROR_USSD_NET_NO_RETURN = 111019 288 | ERROR_USSD_NET_OVERTIME = 111020 289 | ERROR_USSD_TOO_LONG_CONTENT = 111014 290 | ERROR_USSD_XML_SPECIAL_CHARACTER_TRANSFER_FAILED = 111021 291 | ERROR_WIFI_PBC_CONNECT_FAILED = 117003 292 | ERROR_WIFI_STATION_CONNECT_AP_PASSWORD_ERROR = 117001 293 | ERROR_WIFI_STATION_CONNECT_AP_WISPR_PASSWORD_ERROR = 117004 294 | ERROR_WIFI_WEB_PASSWORD_OR_DHCP_OVERTIME_ERROR = 117002 295 | 296 | // My own guess, don't trust this completely. 297 | // Unknown URLs are 100002 298 | ``` 299 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hsp-dev/huawei-api", 3 | "type": "library", 4 | "description": "A barebones API to interact with the Huawei E5180 router", 5 | "homepage": "https://github.com/HSPDev/Huawei-E5180-API", 6 | "keywords": ["api","E5180"], 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Henrik Sylvester Pedersen", 11 | "email": "hsp@hsp.dk", 12 | "homepage": "http://blog.hsp.dk", 13 | "role": "Developer" 14 | } 15 | ], 16 | "autoload": { 17 | "psr-0" : { 18 | "HSPDev\\HuaweiApi" : "src" 19 | } 20 | }, 21 | "require": { 22 | "php": ">=5.3.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /demo.php: -------------------------------------------------------------------------------- 1 | setAddress('192.168.8.1'); 10 | 11 | //Username and password. 12 | //Username is always admin as far as I can tell. 13 | $router->login('admin', 'your-password'); 14 | 15 | var_dump($router->getLedStatus()); 16 | -------------------------------------------------------------------------------- /src/HSPDev/HuaweiApi/CustomHttpClient.php: -------------------------------------------------------------------------------- 1 | manualCookieData = $cookie; 30 | $this->requestToken = $token; 31 | } 32 | 33 | /** 34 | * We need the current token to make the login hash. 35 | */ 36 | public function getToken() 37 | { 38 | return $this->requestToken; 39 | } 40 | 41 | /** 42 | * Builds the Curl Object. 43 | */ 44 | private function getCurlObj($url, $headerFields = []) 45 | { 46 | $ch = curl_init(); 47 | 48 | //curl_setopt($ch, CURLOPT_VERBOSE, true); // DEBUGGING 49 | 50 | $header = [ 51 | 'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12', 52 | 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8;charset=UTF-8', 53 | 'Accept-Language: da-DK,da;q=0.8,en-US;q=0.6,en;q=0.4', 54 | 'Accept-Charset: utf-8;q=0.7,*;q=0.7', 55 | 'Keep-Alive: 115', 56 | 'Connection: keep-alive', 57 | //The router expects these two to be there, but empty, when not in use. 58 | 'Cookie: '.$this->manualCookieData, 59 | '__RequestVerificationToken: '.$this->requestToken, 60 | ]; 61 | foreach ($headerFields as $h) { 62 | $header[] = $h; 63 | } 64 | 65 | curl_setopt($ch, CURLOPT_URL, $url); 66 | 67 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 68 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->connectionTimeout); 69 | curl_setopt($ch, CURLOPT_TIMEOUT, $this->responseTimeout); 70 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); 71 | curl_setopt($ch, CURLOPT_ENCODING, 'gzip'); //The router is fine with this, so no problem. 72 | curl_setopt($ch, CURLOPT_HTTPHEADER, $header); 73 | 74 | //The router rotates tokens in the response headers randomly, so we will parse them all. 75 | curl_setopt($ch, CURLOPT_HEADERFUNCTION, [$this, 'HandleHeaderLine']); 76 | 77 | return $ch; 78 | } 79 | 80 | //end function 81 | 82 | /** 83 | * Makes HTTP POST requests containing XML data to the router. 84 | */ 85 | public function postXml($url, $xmlString) 86 | { 87 | //The API wants it like this. 88 | $ch = $this->getCurlObj($url, ['Content-Type: text/plain; charset=UTF-8', 'Cookie2: $Version=1']); 89 | 90 | curl_setopt($ch, CURLOPT_POST, 1); 91 | curl_setopt($ch, CURLOPT_POSTFIELDS, $xmlString); 92 | 93 | $result = curl_exec($ch); 94 | curl_close($ch); 95 | if (!$result) { 96 | throw new \Exception('A network error occured with cURL.'); 97 | } 98 | 99 | return $result; 100 | } 101 | 102 | /** 103 | * Handles the HTTP Response header lines from cURL requests, 104 | * so we can extract all those tokens. 105 | */ 106 | public function HandleHeaderLine($curl, $header_line) 107 | { 108 | 109 | /* 110 | * Not the prettiest way to parse it out, but hey it works. 111 | * If adding more or changing, remember the trim() call 112 | * as the strings have nasty null bytes. 113 | */ 114 | if (strpos($header_line, '__RequestVerificationTokenOne') === 0) { 115 | $token = trim(substr($header_line, strlen('__RequestVerificationTokenOne:'))); 116 | $this->requestTokenOne = $token; 117 | } elseif (strpos($header_line, '__RequestVerificationTokenTwo') === 0) { 118 | $token = trim(substr($header_line, strlen('__RequestVerificationTokenTwo:'))); 119 | $this->requestTokenTwo = $token; 120 | } elseif (strpos($header_line, '__RequestVerificationToken') === 0) { 121 | $token = trim(substr($header_line, strlen('__RequestVerificationToken:'))); 122 | $this->requestToken = $token; 123 | } elseif (strpos($header_line, 'Set-Cookie:') === 0) { 124 | $cookie = trim(substr($header_line, strlen('Set-Cookie:'))); 125 | $this->manualCookieData = $cookie; 126 | } 127 | 128 | return strlen($header_line); 129 | } 130 | 131 | /** 132 | * Performs a HTTP GET to the specified URL. 133 | */ 134 | public function get($url) 135 | { 136 | $ch = $this->getCurlObj($url); 137 | 138 | $result = curl_exec($ch); 139 | curl_close($ch); 140 | if (!$result) { 141 | throw new \Exception('A network error occured with cURL.'); 142 | } 143 | 144 | return $result; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/HSPDev/HuaweiApi/Router.php: -------------------------------------------------------------------------------- 1 | http = new CustomHttpClient(); 28 | } 29 | 30 | /** 31 | * Sets the router address. 32 | */ 33 | public function setAddress($address) 34 | { 35 | //Remove trailing slash if any. 36 | $address = rtrim($address, '/'); 37 | 38 | //If not it starts with http, we assume HTTP and add it. 39 | if (strpos($address, 'http') !== 0) { 40 | $address = 'http://'.$address; 41 | } 42 | 43 | $this->routerAddress = $address; 44 | } 45 | 46 | /** 47 | * Most API responses are just simple XML, so to avoid repetition 48 | * this function will GET the route and return the object. 49 | * 50 | * @return SimpleXMLElement 51 | */ 52 | public function generalizedGet($route) 53 | { 54 | //Makes sure we are ready for the next request. 55 | $this->prepare(); 56 | 57 | $xml = $this->http->get($this->getUrl($route)); 58 | $obj = new \SimpleXMLElement($xml); 59 | 60 | //Check for error message 61 | if (property_exists($obj, 'code')) { 62 | throw new \UnexpectedValueException('The API returned error code: '.$obj->code); 63 | } 64 | 65 | return $obj; 66 | } 67 | 68 | /** 69 | * Gets the current router status. 70 | * 71 | * @return SimpleXMLElement 72 | */ 73 | public function getStatus() 74 | { 75 | return $this->generalizedGet('api/monitoring/status'); 76 | } 77 | 78 | /** 79 | * Gets traffic statistics (numbers are in bytes). 80 | * 81 | * @return SimpleXMLElement 82 | */ 83 | public function getTrafficStats() 84 | { 85 | return $this->generalizedGet('api/monitoring/traffic-statistics'); 86 | } 87 | 88 | /** 89 | * Gets monthly statistics (numbers are in bytes) 90 | * This probably only works if you have setup a limit. 91 | * 92 | * @return SimpleXMLElement 93 | */ 94 | public function getMonthStats() 95 | { 96 | return $this->generalizedGet('api/monitoring/month_statistics'); 97 | } 98 | 99 | /** 100 | * Info about the current mobile network. (PLMN info). 101 | * 102 | * @return SimpleXMLElement 103 | */ 104 | public function getNetwork() 105 | { 106 | return $this->generalizedGet('api/net/current-plmn'); 107 | } 108 | 109 | /** 110 | * Gets the current craddle status. 111 | * 112 | * @return SimpleXMLElement 113 | */ 114 | public function getCraddleStatus() 115 | { 116 | return $this->generalizedGet('api/cradle/status-info'); 117 | } 118 | 119 | /** 120 | * Get current SMS count. 121 | * 122 | * @return SimpleXMLElement 123 | */ 124 | public function getSmsCount() 125 | { 126 | return $this->generalizedGet('api/sms/sms-count'); 127 | } 128 | 129 | /** 130 | * Get current WLAN Clients. 131 | * 132 | * @return SimpleXMLElement 133 | */ 134 | public function getWlanClients() 135 | { 136 | return $this->generalizedGet('api/wlan/host-list'); 137 | } 138 | 139 | /** 140 | * Get notifications on router. 141 | * 142 | * @return SimpleXMLElement 143 | */ 144 | public function getNotifications() 145 | { 146 | return $this->generalizedGet('api/monitoring/check-notifications'); 147 | } 148 | 149 | /** 150 | * Gets the SMS inbox. 151 | * Page parameter is NOT null indexed and starts at 1. 152 | * I don't know if there is an upper limit on $count. Your milage may vary. 153 | * unreadPrefered should give you unread messages first. 154 | * 155 | * @return bool 156 | */ 157 | public function setLedOn($on = false) 158 | { 159 | //Makes sure we are ready for the next request. 160 | $this->prepare(); 161 | 162 | $ledXml = ''.($on ? '1' : '0').''; 163 | $xml = $this->http->postXml($this->getUrl('api/led/circle-switch'), $ledXml); 164 | $obj = new \SimpleXMLElement($xml); 165 | //Simple check if login is OK. 166 | return (string) $obj == 'OK'; 167 | } 168 | 169 | /** 170 | * Checks whatever we are logged in. 171 | * 172 | * @return bool 173 | */ 174 | public function getLedStatus() 175 | { 176 | $obj = $this->generalizedGet('api/led/circle-switch'); 177 | if (property_exists($obj, 'ledSwitch')) { 178 | if ($obj->ledSwitch == '1') { 179 | return true; 180 | } 181 | } 182 | 183 | return false; 184 | } 185 | 186 | /** 187 | * Checks whatever we are logged in. 188 | * 189 | * @return bool 190 | */ 191 | public function isLoggedIn() 192 | { 193 | $obj = $this->generalizedGet('api/user/state-login'); 194 | if (property_exists($obj, 'State')) { 195 | /* 196 | * Logged out seems to be -1 197 | * Logged in seems to be 0. 198 | * What the hell? 199 | */ 200 | if ($obj->State == '0') { 201 | return true; 202 | } 203 | } 204 | 205 | return false; 206 | } 207 | 208 | /** 209 | * Gets the SMS inbox. 210 | * Page parameter is NOT null indexed and starts at 1. 211 | * I don't know if there is an upper limit on $count. Your milage may vary. 212 | * unreadPrefered should give you unread messages first. 213 | * 214 | * @return SimpleXMLElement 215 | */ 216 | public function getInbox($page = 1, $count = 20, $unreadPreferred = false) 217 | { 218 | //Makes sure we are ready for the next request. 219 | $this->prepare(); 220 | 221 | $inboxXml = ' 222 | '.$page.' 223 | '.$count.' 224 | 1 225 | 0 226 | 0 227 | '.($unreadPreferred ? '1' : '0').' 228 | 229 | '; 230 | $xml = $this->http->postXml($this->getUrl('api/sms/sms-list'), $inboxXml); 231 | $obj = new \SimpleXMLElement($xml); 232 | 233 | return $obj; 234 | } 235 | 236 | /** 237 | * Deletes an SMS by ID, also called "Index". 238 | * The index on the Message object you get from getInbox 239 | * will contain an "Index" property with a value like "40000" and up. 240 | * Note: Will return true if the Index DOES NOT exist already. 241 | * 242 | * @return bool 243 | */ 244 | public function deleteSms($index) 245 | { 246 | //Makes sure we are ready for the next request. 247 | $this->prepare(); 248 | 249 | $deleteXml = ' 250 | '.$index.' 251 | 252 | '; 253 | $xml = $this->http->postXml($this->getUrl('api/sms/delete-sms'), $deleteXml); 254 | $obj = new \SimpleXMLElement($xml); 255 | //Simple check if login is OK. 256 | return (string) $obj == 'OK'; 257 | } 258 | 259 | /** 260 | * Sends SMS to specified receiver. I don't know if it works for foreign numbers, 261 | * but for local numbers you can just specifiy the number like you would normally 262 | * call it and it should work, here in Denmark "42952777" etc (mine). 263 | * Message parameter got the normal SMS restrictions you know and love. 264 | * 265 | * @return bool 266 | */ 267 | public function sendSms($receiver, $message) 268 | { 269 | //Makes sure we are ready for the next request. 270 | $this->prepare(); 271 | 272 | /* 273 | * Note how it wants the length of the content also. 274 | * It ALSO wants the current date/time wtf? Oh well.. 275 | */ 276 | $sendSmsXml = ' 277 | -1 278 | 279 | '.$receiver.' 280 | 281 | 282 | '.$message.' 283 | '.strlen($message).' 284 | 1 285 | '.date('Y-m-d H:i:s').' 286 | 0 287 | 288 | '; 289 | $xml = $this->http->postXml($this->getUrl('api/sms/send-sms'), $sendSmsXml); 290 | $obj = new \SimpleXMLElement($xml); 291 | //Simple check if login is OK. 292 | return (string) $obj == 'OK'; 293 | } 294 | 295 | /** 296 | * Not all methods may work if you don't login. 297 | * Please note that the router is pretty aggressive 298 | * at timing your session out. 299 | * Call something periodically or just relogin on error. 300 | * 301 | * @return bool 302 | */ 303 | public function login($username, $password) 304 | { 305 | //Makes sure we are ready for the next request. 306 | $this->prepare(); 307 | 308 | /* 309 | * Note how the router wants the password to be the following: 310 | * 1) Hashed by SHA256, then the raw output base64 encoded. 311 | * 2) The username is appended with the result of the above, 312 | * AND the current token. Yes, the password changes everytime 313 | * depending on what token we got. This really fucks with scrapers. 314 | * 3) The string from above (point 2) is then hashed by SHA256 again, 315 | * and the raw output is once again base64 encoded. 316 | * 317 | * This is how the router login process works. So the password being sent 318 | * changes everytime depending on the current user session/token. 319 | * Not bad actually. 320 | */ 321 | $loginXml = ' 322 | '.$username.' 323 | 4 324 | '.base64_encode(hash('sha256', $username.base64_encode(hash('sha256', $password, false)).$this->http->getToken(), false)).' 325 | 326 | '; 327 | $xml = $this->http->postXml($this->getUrl('api/user/login'), $loginXml); 328 | $obj = new \SimpleXMLElement($xml); 329 | //Simple check if login is OK. 330 | return (string) $obj == 'OK'; 331 | } 332 | 333 | /** 334 | * Sets the data switch to enable or disable the mobile connection. 335 | * 336 | * @return bool 337 | */ 338 | public function setDataSwitch($value) 339 | { 340 | if (is_int($value) === false) { 341 | throw new \Exception('Parameter can only be integer.'); 342 | } 343 | if ($value !== 0 && $value !== 1) { 344 | throw new \Exception('Parameter can only be integer.'); 345 | } 346 | 347 | //Makes sure we are ready for the next request. 348 | $this->prepare(); 349 | 350 | $dataSwitchXml = ''.$value.''; 351 | 352 | $xml = $this->http->postXml($this->getUrl('api/dialup/mobile-dataswitch'), $dataSwitchXml); 353 | $obj = new \SimpleXMLElement($xml); 354 | 355 | //Simple check if login is OK. 356 | return (string) $obj == 'OK'; 357 | } 358 | 359 | /** 360 | * Internal helper that lets us build the complete URL 361 | * to a given route in the API. 362 | * 363 | * @return string 364 | */ 365 | private function getUrl($route) 366 | { 367 | return $this->routerAddress.'/'.$route; 368 | } 369 | 370 | /** 371 | * Makes sure that we are ready for API usage. 372 | */ 373 | private function prepare() 374 | { 375 | //Check to see if we have session / token. 376 | if (strlen($this->sessionInfo) == 0 || strlen($this->tokenInfo) == 0) { 377 | //We don't have any. Grab some. 378 | $xml = $this->http->get($this->getUrl('api/webserver/SesTokInfo')); 379 | $obj = new \SimpleXMLElement($xml); 380 | if (!property_exists($obj, 'SesInfo') || !property_exists($obj, 'TokInfo')) { 381 | throw new \RuntimeException('Malformed XML returned. Missing SesInfo or TokInfo nodes.'); 382 | } 383 | //Set it for future use. 384 | $this->http->setSecurity($obj->SesInfo, $obj->TokInfo); 385 | } 386 | } 387 | } 388 | --------------------------------------------------------------------------------