Skip to content

Instantly share code, notes, and snippets.

@kolypto
Created September 26, 2023 14:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kolypto/7ff7008d2267de424ccc95ac5b10fcba to your computer and use it in GitHub Desktop.
Save kolypto/7ff7008d2267de424ccc95ac5b10fcba to your computer and use it in GitHub Desktop.
Starline API: работа с сигнализацией Starline из Go

Starline API

Чтобы получить доступ, нужно войти под своей учетной записью на my.starline.ru и перейти на страницу https://my.starline.ru/developer. После заполнения формы, заявку на предоставление доступа к API для аккаунта рассмотрят сотрудники StarLine.

Получение кода приложения для дальнейшего получения токена. Срок годности кода приложения – 1 час.

$ http GET https://id.starline.ru/apiV3/application/getCode appId==123456 secret==(echo -n "<secret>" | md5sum | cut -d' ' -f1)

{
    "desc": {
        "code": "9a28848db17ef477f4896ac4ecf360ad"
    },
    "state": 1
}

Получение токена приложения для дальнейшей авторизации. Время жизни токена приложения – 4 часа.

$ http GET https://id.starline.ru/apiV3/application/getToken appId==123456 secret==(echo -n "<secret><code>" | md5sum | cut -d' ' -f1)

{
    "desc": {
        "token": "b8acd7d7fc95d3bfe1e4228f6374738fb9591fd79fb1937d7258916935463276"
    },
    "state": 1
}

Получение user_token:

$ http --form POST https://id.starline.ru/apiV3/user/login Token:b8acd7d7fc95d3bfe1e4228f6374738fb9591fd79fb1937d7258916935463276 login=user@example.com pass=(echo -n "<password>" | sha1sum | cut -d' ' -f1)

{
    "desc": {
        "_check_password_strength": 1,
        "auth_contact_id": null,
        "avatar": "default",
        "company_name": "",
        "date_register": "2015-02-11 19:17:22",
        "first_name": "John",
        "gmt": "+3",
        "id": "111222",
        "lang": "ru",
        "last_auth_date": "2023-09-22 23:01:09",
        "last_auth_ip": "109.168.229.180",
        "last_name": "Smith",
        "login": "UserName",
        "middle_name": "",
        "roles": [
            "user",
            "open-api-user"
        ],
        "sex": "M",
        "state": "ACTIVE",
        "subscription": null,
        "user_token": "11b1b9600d18d3e2c26777aea56ddd16:238311"
    },
    "state": 1
}

Полученный в результате успешного выполнения команды cookie необходимо использовать в методах WebAPI. Данный токен действителен 24 часа.

$ http POST https://developer.starline.ru/json/v2/auth.slid slid_token=11b1b9600d18d3e2c26777aea56ddd16:111222
Set-Cookie: slnet=BA3D4AB891E5E576A12C57B0812D4F99;
{
    "code": "200",
    "codestring": "OK",
    "nchan_id": "DF0EC51FE9481C57DBE4706E0BB25ED8",
    "realplexor_id": "DF0EC51FE9481C57DBE4706E0BB25ED8",
    "user_id": "111222"
}

Теперь можно получать информацию о машине:

$ http GET https://developer.starline.ru/json/v1/user/<user_id>/user_info Cookie:slnet=BA3D4AB891E5E576A12C57B0812D4F99
{
    "code": 200,
    "codestring": "OK",
    "devices": [
        {
            "alias": "Lada Kalina",
            "balance": 865,
            "battery": 12.73,
            "car_alr_state": {
                "add_h": null,
                "add_l": null,
                "door": 2,
                "hbrake": null,
                "hijack": null,
                "hood": 2,
                "ign": 2,
                "pbrake": null,
                "shock_h": null,
                "shock_l": null,
                "tilt": null,
                "trunk": 2
            },
            "car_state": {
                "add_sens_bpass": 0,
                "alarm": 2,
                "arm": 1,
                "door": 2,
                "dvr": 0,
                "hbrake": 2,
                "hijack": 2,
                "hood": 2,
                "ign": 2,
                "out": 2,
                "pbrake": 2,
                "r_start": 2,
                "relay": 0,
                "run": 2,
                "shock_bpass": 0,
                "tilt_bpass": 0,
                "trunk": 2,
                "valet": 2,
                "webasto": 2
            },
            "ctemp": 29,
            "device_id": "11223344",
            "diag": {
                "can_descr": "5271",
                "can_version": "4.4.0",
                "vin": ""
            },
            "etemp": 74,
            "fw_version": "FG33-P4,GK74-P7",
            "gps_lvl": 0,
            "gsm_lvl": 6,
            "hchan_channel": null,
            "imei": "112233445566",
            "mayak_temp": 865,
            "mon_type": 2,
            "phone": "+79991112233",
            "position": {
                "dir": null,
                "r": 179,
                "s": null,
                "sat_qty": null,
                "ts": 748729163,
                "x": "00.000000",
                "y": "00.000000"
            },
            "reg": null,
            "rpl_channel": null,
            "sn": null,
            "status": 2,
            "ts_activity": 748700007,
            "type": 10
        }
    ],
    "shared_devices": []
}
// LICENSE: MIT
// Author: Mark Vartanyan <kolypto@gmail.com>
package main
import (
"crypto/md5"
"crypto/sha1"
"encoding/hex"
"fmt"
"strconv"
"time"
"github.com/cockroachdb/errors"
"github.com/go-resty/resty/v2"
)
func main() {
for {
starline := NewStarlineClient()
err := starline.Authenticate(12345, "<app-secret>", "user@example.com", "<password>")
if err != nil {
fmt.Printf("err: %+v\n", err)
// Sleep, reconnect
time.Sleep(10 * time.Second)
}
for {
info, err := starline.GetUserInfo()
if errors.Is(err, ErrUnauthorized) {
fmt.Printf("err: Unauthorized. Need to sign in again\n")
fmt.Printf("err: %v\n", err)
return
}
if err != nil {
fmt.Printf("err: %+v\n", err)
break
}
fmt.Printf("info: %+v\n", info)
isCarRunning := info.Devices[0].CarState.Ignition == 0
fmt.Printf("isCarRunning: %v\n", isCarRunning)
// {Ignition:2}: parked
// Give it a break
time.Sleep(60 * time.Second)
}
}
}
func NewStarlineClient() *StarlineClient {
return &StarlineClient{
client: resty.New(),
}
}
type StarlineClient struct {
client *resty.Client
// User id from authentication
userId string
}
func (s *StarlineClient) Authenticate(appId int, appSecret string, login string, password string) error {
// Получение кода приложения для дальнейшего получения токена. Срок годности кода приложения – 1 час.
// $ http GET https://id.starline.ru/apiV3/application/getCode \
// appId==123456 secret==(echo -n "SeCrEt" | md5sum | cut -d' ' -f1)
// { "desc": { "code": "9a28848db17ef477f4896ac4ecf360ad" }, "state": 1 }
appSecretMd5Bytes := md5.Sum([]byte(appSecret))
appSecretMd5 := hex.EncodeToString(appSecretMd5Bytes[:])
var getCodeResult struct {
// Result: 0 failure, 1 ok
State int `json:"state"`
Desc struct {
// Success
Code string `json:"code"`
// Failure
Message string `json:"message"`
} `json:"desc"`
}
res, err := s.client.R().
SetResult(&getCodeResult).
SetQueryParam("appId", strconv.Itoa(appId)).
SetQueryParam("secret", appSecretMd5).
Get("https://id.starline.ru/apiV3/application/getCode")
if DEBUG {
fmt.Printf("getCode:\n")
fmt.Printf(" res.Request.URL: %v\n", res.Request.URL)
fmt.Printf(" getCodeResult: %+v\n", getCodeResult)
fmt.Printf(" res: %v\n", res)
}
if err != nil {
return errors.Wrap(err, "failed to getCode")
}
if getCodeResult.State != 1 {
return errors.Errorf("failed to getCode: %d %s", getCodeResult.State, getCodeResult.Desc.Message)
}
// Получение токена приложения для дальнейшей авторизации. Время жизни токена приложения – 4 часа.
// $ http GET https://id.starline.ru/apiV3/application/getToken \
// appId==123456 secret==(echo -n "<secret><code>" | md5sum | cut -d' ' -f1)
// { "desc": { "token": "b8acd7d7fc95d3bfe1e4228f6374738fb9591fd79fb1937d7258916935463276" }, "state": 1 }
secretWithCodeBytes := md5.Sum([]byte(appSecret + getCodeResult.Desc.Code))
secretWithCode := hex.EncodeToString(secretWithCodeBytes[:])
var getTokenResult struct {
// Result: 0 failure, 1 ok
State int `json:"state"`
Desc struct {
// Success
Token string `json:"token"`
// Failure
Message string `json:"message"`
} `json:"desc"`
}
res, err = s.client.R().
SetResult(&getTokenResult).
SetQueryParam("appId", strconv.Itoa(appId)).
SetQueryParam("secret", secretWithCode).
Get("https://id.starline.ru/apiV3/application/getToken")
if DEBUG {
fmt.Printf("getToken:\n")
fmt.Printf(" res.Request.URL: %v\n", res.Request.URL)
fmt.Printf(" getTokenResult: %+v\n", getTokenResult)
fmt.Printf(" res: %v\n", res)
}
if err != nil {
return errors.Wrap(err, "failed to getToken")
}
if getTokenResult.State != 1 {
return errors.Errorf("failed to getToken: %d %s", getTokenResult.State, getTokenResult.Desc.Message)
}
// Получение user_token
// $ http --form POST https://id.starline.ru/apiV3/user/login \
// Token:b8acd7d7fc95d3bfe1e4228f6374738fb9591fd79fb1937d7258916935463276 \
// login=user@example.com pass=(echo -n "PaSsWoRd" | sha1sum | cut -d' ' -f1)
// { "desc": { "user_token": "11b1b9600d18d3e2c26777aea56ddd16:238311" }, "state": 1 }
passwordSha1Bytes := sha1.Sum([]byte(password))
passwordSha1 := hex.EncodeToString(passwordSha1Bytes[:])
var loginResult struct {
// Result: 0 failure, 1 ok
State int `json:"state"`
Desc struct {
// Success
UserToken string `json:"user_token"`
// Failure
Message string `json:"message"`
} `json:"desc"`
}
res, err = s.client.R().
SetResult(&loginResult).
SetHeader("Token", getTokenResult.Desc.Token).
SetFormData(map[string]string{
"login": login,
"pass": passwordSha1,
}).
Post("https://id.starline.ru/apiV3/user/login")
if DEBUG {
fmt.Printf("login:\n")
fmt.Printf(" res.Request.URL: %v\n", res.Request.URL)
fmt.Printf(" loginResult: %v\n", loginResult)
fmt.Printf(" res: %v\n", res)
}
if err != nil {
return errors.Wrap(err, "failed to login")
}
if loginResult.State != 1 {
return errors.Errorf("failed to login: %d %s", loginResult.State, loginResult.Desc.Message)
}
// Полученный в результате успешного выполнения команды cookie необходимо использовать в методах WebAPI.
// Данный токен действителен 24 часа.
// $ http POST https://developer.starline.ru/json/v2/auth.slid \
// slid_token=11b1b9600d18d3e2c26777aea56ddd16:238311
// Set-Cookie: slnet=BA3D4AB891E5E576A12C57B0812D4F99;
// { "code": "200", "codestring": "OK" }
var authSlidResult struct {
// "200" ok
Code string `json:"code"`
CodeString string `json:"codestring"`
// Use in URL
UserId string `json:"user_id"`
}
res, err = s.client.R().
SetResult(&authSlidResult).
SetBody(struct {
SlidToken string `json:"slid_token"`
}{
SlidToken: loginResult.Desc.UserToken,
}).
Post("https://developer.starline.ru/json/v2/auth.slid")
if DEBUG {
fmt.Printf("auth.slid:\n")
fmt.Printf(" res.Request.URL: %v\n", res.Request.URL)
fmt.Printf(" authSlidResult: %v\n", authSlidResult)
fmt.Printf(" res: %v\n", res)
fmt.Printf(" res.Cookies(): %v\n", res.Cookies())
}
if err != nil {
return errors.Wrap(err, "failed to auth.slid")
}
if authSlidResult.Code != "200" {
return errors.Errorf("failed to getToken: %d %s", getTokenResult.State, getTokenResult.Desc.Message)
}
s.userId = authSlidResult.UserId
return nil
}
// Get info about cars
func (s *StarlineClient) GetUserInfo() (result UserInfo, err error) {
// Теперь можно получать информацию о машине:
// $ http GET https://developer.starline.ru/json/v1/user/<user_id>/user_info \
// Cookie:slnet=BA3D4AB891E5E576A12C57B0812D4F99
res, err := s.client.R().
SetResult(&result).
Get("https://developer.starline.ru/json/v1/user/" + s.userId + "/user_info")
if DEBUG {
fmt.Printf("user_info:\n")
fmt.Printf(" res.Request.URL: %v\n", res.Request.URL)
fmt.Printf(" result: %+v\n", result)
fmt.Printf(" res: %v\n", res)
fmt.Printf(" res.Request.Cookies: %v\n", res.Request.Cookies)
}
if result.Code == 403 {
err = errors.CombineErrors(ErrUnauthorized, err)
return
}
if result.Code != 200 {
err = errors.Errorf("failed to get user info: %d %s", result.Code, result.CodeString)
return
}
return
}
// Unauthorized. Need to sign in.
var ErrUnauthorized = errors.New("Unauthorized")
type UserInfo struct {
Code int `json:"code"`
CodeString string `json:"codestring"`
// Cars
Devices []StarlineDevice
}
type StarlineDevice struct {
Alias string `json:"alias"`
CarState struct {
// Ignition running?
// 2 = off, 1 = engine running
Ignition int `json:"ign"`
} `json:"car_state"`
}
// Is the engine running?
func (d StarlineDevice) IsEngineRunning() bool {
return d.CarState.Ignition == 1
}
const DEBUG = false
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment