Skip to content

Instantly share code, notes, and snippets.

@malys
Last active February 11, 2025 17:49
Show Gist options
  • Save malys/5203cdf15000bd64d1da800a005bab55 to your computer and use it in GitHub Desktop.
Save malys/5203cdf15000bd64d1da800a005bab55 to your computer and use it in GitHub Desktop.
Dashboard for SAIC Electrical Vehicles (MG4)
/check.sh
/export.sh
/node_modules/
/scripts/
/package-lock.json
/dashboardTmp.png
/dashboardTmp2.png

Please visit github repository for the lastest updates.

See Nodered flow page

Goals

My main goal is to integrate my MG4 in Node-red to use car's data with my automation workflow. Moreover, SAIC's ISmart mobile app is limited that why I have developed this dashboard.

Features

Dash-MG4: flow.json

  • Support of Multi-language supported (default: French)
  • Dashboard with car's components status
  • Localization of my car on a map (daily, weekly, monthly and custom journeys)
  • Compute charge cost and duration using electricity pricing limplementation (EDF in France)
  • Override Saic MQTT gateway configuration
  • Charts for:
    • Tyres pressure
    • Battery voltage
    • Consumption + cost
    • Temperature
    • Battery capacity
    • Charge calendar
  • Remote actions
    • set remote temperature, A/C mode, target SOC
    • enable A/C, lock, boot ...

MG4 Gateway: flow_gateway.json

  • Manage MQTT SAIC Gateway instance.

Miscellaneous

These features are not includes in theses flows but they have been integrated:

  • Alerting (opened windows or doors at night, tyres pressure ...)
  • Closing automatically car at night
  • Starting automatically A/C
  • Integration with Google Home Ok Google MG4 -> Your car is in the avenue Champs-Elyseés, 7 near Mc Donalds restaurant ...

Dependencies

I use Saic MQTT gateway project to query data from MG's car.

Dashboard integrates:

  • MQTT input to query data from local Moquitto
  • nodered-dashboard-ui providing ui components
  • node-red-contrib-web-worldmap as map provider
  • node-red-contrib-ui-svg to integrate MG top view from official MG website
  • EDF pricing data file to compute cost
  • Persistance in SQLite database (in beta, to remove for production)

Installation guide

No warrantly, No support, No security requirements

Schema

Raspberry Pie

# Slow process
https://raw.githubusercontent.com/tvdsluijs/sh-python-installer/main/python.sh | sudo bash -s 3.xx.x
# Default folder: /home/dietpi/.node-red/saic/
mkdir /home/dietpi/.node-red/
git clone --depth 1 https://github.com/SAIC-iSmart-API/saic-python-mqtt-gateway.git
mv saic-python-mqtt-gateway saic
cd saic
pip3 install -r requirements.txt
  • Launch manually SAIC Gateway for testing
# Launch gateway
python mqtt_gateway.py -m tcp://localhost:1883 -u "${saic email}" -p "${saic password}" --mqtt-user "mosquitto"  --mqtt-password "${mqtt password}"
  • Use MQTT Explorer to get path of data (ex: saic/xxx/vehicles/yyy)
    • xxx: account ID
    • yyy: vehicule ID

NodeRed

dietpi-software install 9
# or
dietpi-software reinstall 9
  • Install NodeRed
  • Enable Saving context data to the file-system
  • Create a folder /data/ for mg.db sqlite database or change MGDB node to define database path
  • Import flow.json: Dash-MG4
  • Import flow_gateway.json: MG4 Gateway to manage SAIC Gateway instance in NodeRed
  • Configuration:
    • open SAICMQTTFx/credentialsSAIC node:
server: https://tap-eu.soimt.com (for europe)
gateway: https://gateway-eu.soimt.com (for europe)
email: (email address of your MG ISmart account)
password: (password of your MG ISmart account)
ABRP_TOKEN: (bxxxx-xxx-xxx-xx-xxxx see ABRP integration)
ABRP_API_KEY: (xxx-xx-xx-xxx-xxxxx see ABRP integration)
GATEWAY_FOLDER: (absolute path of your gateway installation folder if you want to use **flow_gateway.json**)
VIN: (vin of your vehicle)
  • open SAICMQTTFx/credentialMQTTFx node:
MQTT_URL: (xxxxxx:1883)
MQTT_USER: (mqtt user)
MQTT_PASSWORD: (mqtt password)
MQTT_CLIENTID: (mqtt client id)
  • Deploy them
  • Watch Saic MQTT gateway outputs & see debug trace in Nodered
    • you have to see before MQTT gateway events
    • after that MQTT input in NodeRed will integrates data
  • Open http://${ip}:1880/ui/

Contribution

This flow is provided as it.

Feel free:

  • to improve it
  • to fix some bugs
  • to share your work ;)

Disseminating knowledge is the human duty, sharing it about so that all can benefit.

Translation

  • Edit saicInternationalFx subflow
  • Duplicate actionFR and rename actionXX
  • Translate everything in XX
  • Implement electricity functions
  • Edit label node and add XX in global.get("saic").language_supported (ex: XX=ES for spanish)
In Action

  • Test, test and test

  • Share with me actionXX && global.get("saic").language_supported contents

  • I will release a new version of this dashboard and you are the official maintainer of this language

  • Well done !

License

GNU GPLv3

Thanks

"Buy Me A Coffee"

This file has been truncated, but you can view the full file.
View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

@malys
Copy link
Author

malys commented Feb 10, 2025

am just testing the dashboard with a view to creating voice automations for some/all of the functionality.

I hit an error opening the boot. Looks like the wrong symbol type is being sent via mqtt. I'm not sure whether that's originating in your code or in the gateway.

9 Feb 23:29:16 - [warn] [function:set] doors_boot true false
2025-02-09 23:29:18.442 FATAL  Logger               Unhandled error detected: The "string" argument must be of type string or an instance of Buffer or ArrayBuffer. Received type boolean (true)
  at Function.byteLength (node:buffer:774:11)
  at publish (/home/jpadie/.node-red/node_modules/mqtt-packet/writeToStream.js:338:51)
  at Object.generate [as writeToStream] (/home/jpadie/.node-red/node_modules/mqtt-packet/writeToStream.js:35:14)
  at MqttClient._writePacket (/home/jpadie/.node-red/node_modules/mqtt/build/lib/client.js:934:46)
  at MqttClient._sendPacket (/home/jpadie/.node-red/node_modules/mqtt/build/lib/client.js:984:22)
  at publishProc (/home/jpadie/.node-red/node_modules/mqtt/build/lib/client.js:373:26)
  at MqttClient.publish (/home/jpadie/.node-red/node_modules/mqtt/build/lib/client.js:380:14)
  at /home/jpadie/.node-red/node_modules/mqtt/build/lib/client.js:391:18
  at new Promise (<anonymous>)
  at MqttClient.publishAsync (/home/jpadie/.node-red/node_modules/mqtt/build/lib/client.js:390:16)
9 Feb 23:29:18 - [red] Uncaught Exception:
9 Feb 23:29:18 - [error] TypeError [ERR_INVALID_ARG_TYPE]: The "string" argument must be of type string or an instance of Buffer or ArrayBuffer. Received type boolean (true)
    at Function.byteLength (node:buffer:774:11)
    at publish (/home/jpadie/.node-red/node_modules/mqtt-packet/writeToStream.js:338:51)
    at Object.generate [as writeToStream] (/home/jpadie/.node-red/node_modules/mqtt-packet/writeToStream.js:35:14)
    at MqttClient._writePacket (/home/jpadie/.node-red/node_modules/mqtt/build/lib/client.js:934:46)
    at MqttClient._sendPacket (/home/jpadie/.node-red/node_modules/mqtt/build/lib/client.js:984:22)
    at publishProc (/home/jpadie/.node-red/node_modules/mqtt/build/lib/client.js:373:26)
    at MqttClient.publish (/home/jpadie/.node-red/node_modules/mqtt/build/lib/client.js:380:14)
    at /home/jpadie/.node-red/node_modules/mqtt/build/lib/client.js:391:18
    at new Promise (<anonymous>)
    at MqttClient.publishAsync (/home/jpadie/.node-red/node_modules/mqtt/build/lib/client.js:390:16)
nodered.service: Main process exited, code=exited, status=1/FAILURE
nodered.service: Failed with result 'exit-code'.

Update saic gateway to last stable version , debug mqtt publish message. (debug before mqtt-auto-publish node)
The serialization between js and python for boolean is tricky! true (js) !=True (python)

If you find something, please share it to fix it on the stream code.

I'm maintaining only French part. The other translations are from contributions.

@jpadie
Copy link

jpadie commented Feb 10, 2025

I'm maintaining only French part. The other translations are from contributions.

donc préfêres-tu qu'ici on discute en francais? J'utilise la version Francaise du flow.

the gist was reinstalled yesterday and of course the api code versions are hard coded via requirements.txt.

the debug message that your function outputs before auto_publish is

doors_boot true false

However this message is emitted by a node.warn inside a function (aa082cea1bcbc50e) and the flow never reaches the auto_publish node. There is no debug from immediately prior the auto_publish node.

Assuming that the crash interrupts the debugging I inserted a 5s delay before auto_publish. This worked as expected and the message immediately prior to the auto_publish node sets the payload as boolean true and the topic to boot/set as expected.

so I conclude that the error is not within the flow itself but either in mqtt_auto_publish node or the code that it calls.

The code for the mqtt-auto nodes theselves is trivial and unlikely to cause issues. It looks most likely that the issue is caused by the DynMqtt.js wrapper not encapsulating the data correctly before passing to the native mqtt function.

Would it be better to use the standard mqtt nodes?

The serialization between js and python for boolean is tricky! true (js) !=True (python)

sure - but this is not the issue. the error is occurring in the JS code, pre-python calls.

@malys
Copy link
Author

malys commented Feb 10, 2025

I can't reproduce the problem
"Would it be better to use the standard mqtt nodes?"
=> standard mqtt node plugin is not dynamical.
I have developped this extension to have something with parameters and dynamical (check https://github.com/malys/node-red-contrib-mqtt-auto)

Do you use ?
@malysus/node-red-contrib-mqtt-auto
2.2.0
which mqtt broker do you use?

@malys
Copy link
Author

malys commented Feb 10, 2025

yes

10 Feb 18:58:31 - [error] TypeError [ERR_INVALID_ARG_TYPE]: The "string" argument must be of type string or an instance of Buffer or ArrayBuffer. Received type boolean (true)
2025-02-10 17:58:32
     at Function.byteLength (node:buffer:774:11)
2025-02-10 17:58:32
     at publish (/data/node_modules/@malysus/node-red-contrib-mqtt-auto/node_modules/mqtt-packet/writeToStream.js:338:51)
2025-02-10 17:58:32
     at Object.generate [as writeToStream] (/data/node_modules/@malysus/node-red-contrib-mqtt-auto/node_modules/mqtt-packet/writeToStream.js:35:14)
2025-02-10 17:58:32
     at MqttClient2025-02-10T17:58:32.352Z ._writePacket (/data/node_modules/@malysus/node-red-contrib-mqtt-auto/node_modules/mqtt/build/lib/client.js:934:46)
2025-02-10 17:58:32
     at MqttClient._sendPacket (/data/node_modules/@malysus/node-red-contrib-mqtt-auto/node_modules/mqtt/build/lib/client.js:984:22)
2025-02-10 17:58:32
     at publishProc (/data/node_modules/@malysus/node-red-contrib-mqtt-auto/node_modules/mqtt/build/lib/client.js:373:26)
2025-02-10 17:58:32
     at MqttClient.publish (/data/node_modules/@malysus/node-red-contrib-mqtt-auto/node_modules/mqtt/build/lib/client.js:380:14)
2025-02-10 17:58:32
     at /dat2025-02-10T17:58:32.352Z a/node_modules/@malysus/node-red-contrib-mqtt-auto/node_modules/mqtt/build/lib/client.js:391:18
2025-02-10 17:58:32
     at new Promise (<anonymous>)
2025-02-10 17:58:32
     at MqttClient.publishAsync (/data/node_modules/@malysus/node-red-contrib-mqtt-auto/node_modules/mqtt/build/lib/client.js:390:16)

I will try to fix it

@jpadie
Copy link

jpadie commented Feb 10, 2025

not sure what you mean by dynamical but assuming you mean:

  1. subscribe to mqtt topics by reference to a dynamically provided topic
  2. publish to mqtt server by reference to a dynamically provided topic

then yes, the standard nodes are "dynamical".

e.g.
for subscriptions

let msg = {
action: "subscribe",
topic: [ { topic: "sometopic", qos: 0}, {topic: "someothertopic", qos:0}]
}
return msg;

for publish

let msg = {
payload: JSON.stringify(true),
topic: "sometopic"
}

for connect

let msg = {
action: "connect",
broker: {
  url: "localhost:1883",
  username: "my mqtt username",
  password: "my mqtt password"
}};

I have not tested your code further but I suspect that you could fix the boolean issue like this

// =========== Publisher ===========
    RED.nodes.registerType("mqtt-auto-publish", function (config) {
        RED.nodes.createNode(this, config);
        var node = this;
        node.on('input', async function (msg, send, done) {
            let client = DynMQTT.getClient(msg.client_id);
            let options = msg.options || {};
            switch(typeof msg.payload){
                case "boolean":                        
                        msg.payload = JSON.stringify(msg.payload);
                        break;
            }
            if (client && await client.publish(msg.topic, msg.payload, options)) {
                done();
            }
            else {
                node.warn("Client not known or not connected - sending msg back for re-looping");
                send(msg);
                done();
            }
        })
    });

@malys
Copy link
Author

malys commented Feb 11, 2025

I don't remember exactly but I think that mqtt default nodes are not supporting subscription "/#' (wildcard).
I have updated the mqtt module and the flow.
Currently, database is in '/data/' (see doc) to be compatible with nodered docker image.

Nodered script is not easy to maintain and sometimes i'm introduced regression.

Feel free:
-to improve it
-to fix some bugs
-to share your work ;)

@jpadie
Copy link

jpadie commented Feb 11, 2025

Have you considered porting at least the comms layer and perhaps some of the dashboard components to a package installed via npm?

My thrust will be to abstract some of the core interactive components (e.g. locks, charging, heating etc) to synthetic matter devices that can be natively controlled by google/amazon/apple etc. these won't need the dashboard but use an interface to the client (either direct API calls or via mqtt). I am hoping that this will also provide a voice interface that will work in-car, fairly seamlessly and more extensively than the native MG interface. I absolutely intend to share that work.

I don't remember exactly but I think that mqtt default nodes are not supporting subscription "/#' (wildcard).
Confirmed that wildcard subscriptions work as expected.

@malys
Copy link
Author

malys commented Feb 11, 2025

I have integrated MG4 in Nodered to be a part of my home automation.
Currenty using nodered, I get mg4 informations from Google Home and I have many features not shared:

  • alerting (ntfy, SMS)
  • monitoring
  • time to go
  • integration with google calendar
  • automatic defrost
  • ...

To my mind, Nodered integration it's a easy, short term, quick solution for home automation but it's a mess to share and to maintain.

MQTT publish it's not very stable because of SAIC services and vehicle hibernation.
The magic thing is saic gateway and mqtt broker.

I don't know if it's possible to export a flow to standalone nodejs project.

@jpadie
Copy link

jpadie commented Feb 11, 2025

After a decade of developing embedded systems I've started developing for the node-red ecosystem and prefer, overall, building extensions to complicated flows. I think there are ways to export a flow (one way is just a subflow) but it's more likely to be beneficial to extract the comms layer to a custom node. Consider emulating, for example, Zigbee2Mqtt-like functionality. Then the dashboard can be maintained solus.

MQTT publish it's not very stable because of SAIC services and vehicle hibernation.

it's an interesting use case as there are some actions you would want not to be re-tried after a failure (e.g. unlock). but many that you might want re-tried for a defined timeout. There should be a way to achieve this within the logic but I haven't yet investigated deeply enough.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment