Skip to content

Instantly share code, notes, and snippets.

@DJm00n
Created June 22, 2026 16:15
Show Gist options
  • Select an option

  • Save DJm00n/721a0993543729f53db17cab52454ef1 to your computer and use it in GitHub Desktop.

Select an option

Save DJm00n/721a0993543729f53db17cab52454ef1 to your computer and use it in GitHub Desktop.
MW4 AT Command Reference

MW4 AT Command Reference

Firmware: MW4_01_5429_SS_04_00.00.00.0B (2025-02-06)
SoC: BL602 RISC-V, flash base 0x23000000
Decompiled source: MW4_01_5429_SS_04_decompressed.bin.c


AT Command Interface (Cloud → Device)

The cloud can send AT commands directly to the device. The path is:

Cloud TCP socket (YZSSL task)
  → sub_2309FA8A(data, len)
    → sub_2309F90A(0, data, len, outbuf, 0x400, &outlen)
      → checks for 'A','T','+' prefix
        → sub_2309F81E(cmd_name) → looks up command in table at off_230EF564
          → calls handler(a1, num_args-1, arg_array, outbuf, buflen)

AT command struct format (4 words each):

[0] = char* cmd_name
[1] = handler_fn*   (both GET and SET in one function)
[2] = char* help_string
[3] = NULL (terminator / unused)

The handler receives x11 = len (0 for GET, >0 for SET) and x12 = ptr to argument array.


AT Command Dispatch Architecture

There are three independent dispatch paths → all served on the same TCP port 48899:

Path Dispatcher Table Commands
1 sub_2309F90A → sub_2309F81E off_230EF564 (flash, 16 B/entry) YZ custom commands (YZCATTIME, YZEXPFUN, etc.)
2 sub_2300E016 → sub_2300DEB6 → sub_2300A928 RAM list at 0x4200D1F4 (2 sub-tables) HiFlying standard commands (NETP, UART, WMODE, etc.)
3 Direct sub_230C7CFE string matching No table → inline if/else chain One-off commands (WEBU, AT+Z, BLEADV, WALK, etc.)

AT Command Table → YZ/SolarMan (Path 1)

Table starts at off_230EF564 (0x230ef564). Each entry is 16 bytes.

Entry VA Command Handler VA Role
0x230ef564 TIME 0x230a0bf6 Set/Get device RTC time
0x230ef574 ADDRESS 0x230a0686 Set/Get Modbus slave address
0x230ef584 KEY 0x230a0cc4 Set/Get device access key
0x230ef594 MTYPE 0x230a0d50 Set/Get device model type
0x230ef5a4 CNMOSAVE 0x230a0dd6 Save config to NVRAM
0x230ef5b4 DEVICENUM 0x230a1016 Set/Get number of connected devices
0x230ef5c4 DEVSELCTL 0x230a0f58 Device selection control
0x230ef5d4 NDBGS 0x230a109a Debug settings
0x230ef5e4 FFCLEAR 0x230a1120 Flash/file clear
0x230ef5f4 LANGSEL 0x230a11c0 Set/Get language selection
0x230ef604 ADTIME 0x2309f16e Set/Get device absolute time
0x230ef614 YZFTIME 0x230a1670 Set/Get factory time
0x230ef624 YZEARSE 0x230a0292 Erase flash data
0x230ef634 YZACK 0x230a1704 Acknowledgement command
0x230ef644 YZTIME 0x230a0cc0 Set/Get YZ time
0x230ef654 WKTIME 0x2309f214 Get current time from RTC (GET only; same body as ADTIME)
0x230ef664 ERRCLR 0x2309ecf2 Clear error flags
0x230ef674 YZDBNUM 0x230a18f6 DB entry count
0x230ef684 YZDB 0x230a1ac0 DB access
0x230ef694 YZVER 0x2309ea14 Get firmware version
0x230ef6a4 YZLOG 0x2309f2ae Get log data
0x230ef6b4 YZAPP 0x2309ee46 App control
0x230ef6c4 APSTAT 0x230a1c22 AP status
0x230ef6d4 YZSIZE 0x2309eb64 Flash file sizes
0x230ef6e4 INVDATA 0x2309f3da Inverter raw data
0x230ef6f4 WEBUPURL 0x230a31a2 OTA download URL
0x230ef704 GLTYPE 0x2309e92e Get logger type
0x230ef714 OFFLINENUM 0x2309eab8 Offline record count
0x230ef724 APSOCKET 0x230a1234 AP socket config
0x230ef734 YZWMODE 0x230a12b2 WiFi mode
0x230ef744 YZWAKEYCTL 0x230a1330 WiFi wake key control
0x230ef754 VER 0x2309eb06 Version string
0x230ef764 YZFLSHTEST 0x2309f694 Flash integrity test
0x230ef774 YZRSTTIME 0x230a1cba Set/Get reset timers
0x230ef784 YZBVER 0x2309e932 Bootloader version
0x230ef794 YZFACTEST 0x2309ebfa Factory test
0x230ef7a4 YZPLAN 0x230a11c0 Set/Get plan/schedule digit (0→9)
0x230ef7b4 YZFLASHREAD 0x2309ec7e Read flash sector
0x230ef7c4 YZCATTIME 0x230a13b8 Get upload period byte
0x230ef7d4 YZEXPFUN 0x230a1436 Set upload period byte (2-char decimal)
0x230ef7e4 IVGET 0x2309f75e Inverter data get
0x230ef7f4 YZNETCHECK 0x230a1598 Network health check
0x230ef804 YZPCHECK 0x230a1604 Protocol check
0x230ef814 DTYPE 0x2309ea62 Device type
0x230ef824 IFILE 0x230a3bf0 Internal file access
0x230ef834 HTTPPOSTFILE 0x230a46b6 HTTP POST file upload
0x230ef844 HTTPPOST 0x230a0510 HTTP POST data
0x230ef854 FILEGET 0x2309eeb2 File download
0x230ef864 SDTT 0x2309f4fa SD test/telemetry
0x230ef874 SERCH 0x230a1eb6 Server/host search
0x230ef884 SSLSER 0x230a17dc SSL server config
0x230ef894 YZWSKEYPSD 0x230a00fc WiFi key/password
0x230ef8a4 MQTTSER 0x230a17dc MQTT server (shares handler with SSLSER)
0x230ef8b4 YZSYSVER 0x230a1d5a System version
0x230ef8c4 YZSYSCON 0x230a1dce System connection config
0x230ef8d4 YZSYSKEY 0x230a0378 System key
0x230ef8e4 YZDEFC 0x230a1e42 Default config
0x230ef8f4 TS_SN 0x230a14b4 Set/Get TS serial number
0x230ef904 YZFMODE 0x230a47cc File/flash mode
0x230ef914 YZFNL 0x230a1528 File NL setting
0x230ef924 YZDEVCTIME 0x230a199e Set/Get device timing params
0x230ef934 YZSPEEDREAD 0x230a1a48 Speed read
0x230ef944 WEBCTL 0x230a1f1c Web control
0x230ef954 SKA 0x230a1f9a SKA auth

HiFlying Standard Commands (Path 2)

Registered at boot via sub_23013F4C(). No password required for any of these.

Network / TCP (table off_230E62B4)

Command GET Response SET Format Notes
AT+NETP =TCP,Server,<port>,<ip> AT+NETP=TCP,Server,<port>,<ip> Remote server connection params
AT+NSTAT =Weak/Normal/Good,NN% or =Disconnected N/A WiFi signal quality
AT+TCPDISB current TCP state AT+TCPDISB=on/off Disable TCP
AT+TCPLK =on/=off GET-only SOCKB link status
AT+UDPLK =on/=off GET-only SOCKA UDP status
AT+NTPSER NTP hostname AT+NTPSER=<host> Max 32 chars
AT+DHCPSR =DHCP,ip,mask,gw AT+DHCPSR=DHCP or =STATIC,ip,mask,gw DHCP/static config
AT+TCPTO =<seconds> AT+TCPTO=<0-600> TCP timeout
AT+SOCKB =NONE/TCP/UDP/UDPS,port,ip AT+SOCKB=<proto>,<port>,<ip> Secondary socket config
AT+TCPCS =on/=off AT+TCPCS=on/off SOCKA connection state
AT+TCPB_TO =<seconds> AT+TCPB_TO=<0-600> SOCKB timeout
AT+UARTTM =<ms> AT+UARTTM=<32-1368> UART frame timeout (ms)
AT+UARTTL =<bytes> AT+UARTTL=<10-1002> UART frame length trigger

Socket I/O (table off_230E6394)

Command Format Notes
AT+SSEND AT+SSEND=<n> then n bytes Send n bytes via SOCKA
AT+SSENDB AT+SSENDB=<n> then n bytes Send n bytes via SOCKB
AT+SRECV +ok=<size>,<data> Receive from SOCKA
AT+SRECVB +ok=<size>,<data> Receive from SOCKB

HiFlying SDK Commands

Command GET Response SET Format Notes
AT+VER =3.00.16 (2025-02-06 17:00 8M) GET-only HiFlying module version
AT+UART =baud,bits,stop,parity,flow AT+UART=baud,bits,stop,parity,flow UART parameters
AT+WANN =DHCP,ip,mask,gw AT+WANN=DHCP or =STATIC,ip,mask,gw STA IP config
AT+WMODE =AP/=STA/=APSTA AT+WMODE=AP/STA/APSTA WiFi mode (triggers restart)
AT+WSKEY =OPEN,NONE or =WPA2PSK,AES,<pw> AT+WSKEY=WPA2PSK,AES,<password> WiFi security (8→64 chars)

Direct-Match Commands (Path 3 → inline string matching)

No password required unless noted. Matched before table lookup.

Command GET Response SET Format Notes
AT+WEBU =admin,admin (web credentials) AT+WEBU=<user>,<pass> → confirmed → returns web UI credentials
AT+Z → AT+Z Device reboot (immediate)
AT+RELD +ok=rebooting... AT+RELD Device reboot with message
AT+WALK WiFi AP scan list GET-only Scans and returns nearby APs
AT+WSCAN WiFi scan (short) AT+WSCAN=<ssid> Site survey
AT+WSCANS WiFi scan (extended) AT+WSCANS=<ssid> Extended site survey
AT+WSSSID current SSID AT+WSSSID=<ssid> Set target AP SSID
AT+BLEADV → AT+BLEADV=<payload>,<encoding> Set BLE advertisement payload
AT+BLECONFIG → AT+BLECONFIG=<mode>,<ssid> BLE provisioning mode
AT+BLENTFUUIDW → AT+BLENTFUUIDW=<uuid> Set BLE notify characteristic UUID
AT+BLEINDUUIDW → AT+BLEINDUUIDW=<uuid> Set BLE indicate characteristic UUID
AT+BLEADP → AT+BLEADP=<params> BLE advertising interval
AT+CFGTF → AT+CFGTF Factory config transfer (internal)
AT+YUMV → AT+YUMV=on/off YZ upgrade mode enable/disable
AT+UPURL → AT+UPURL=<url> Firmware OTA URL → triggers upgrade
AT+INVUPURL → AT+INVUPURL=<url> Inverter OTA URL
AT+WEBUPURL → AT+WEBUPURL=<url> Web page OTA URL
AT+SUPDATE → AT+SUPDATE=<url> Software update trigger
AT+UPGRADE → AT+UPGRADE=<url> Upgrade trigger
AT+YWFPW → AT+YWFPW=<password> WiFi password change
AT+WADHCP → AT+WADHCP Trigger DHCP assignment

Complete AT Command Syntax Reference

Authentication notes:

  • Single password → all SET commands that require authentication check against RAM[0x42020960], default 214028. There is no second password.
  • Some handlers call helper sub_230A00DE(arg) which does MEMORY[0x42020960] == sub_230C6026(arg) internally; others (YZRSTTIME, YZDEVCTIME, ADDRESS, YZACK, etc.) do the same comparison inline → behavior is identical.
  • none = no password check at all

Error codes: -1 wrong password or validation failure; -2 wrong arg count (handler-specific); -3 wrong arg count (handler-specific); -4 arg count wrong or value out of range

All SET commands that modify yz_flash config block call sub_230A0BA6() to persist to flash at 0x400000 + 0x480000 (588 bytes). Exceptions noted.


Read-Only Commands (GET only, SET returns -1)

Command Handler GET Response Notes
AT+VER sub_2309EB06 +ok=HF3.00.16 (2025-02-06 17:00 8M) WiFi module version, hardcoded
AT+YZVER sub_2309EA14 +ok=MW4_01_5429_SS_04_00.00.00.0B Firmware version, hardcoded
AT+YZBVER sub_2309E932 +ok=BV<hex8> Bootloader version derived from RAM[0x42020B30]
AT+DTYPE sub_2309EA62 +ok=<type>,<secondary> Device type from RAM[0x42020954], RAM[0x420214DC]
AT+OFFLINENUM sub_2309EAB8 +ok=<count> Offline record counter at RAM[0x42020CB0] (signed long)
AT+YZSIZE sub_2309EB64 +ok=8M FLASH Flash size string; RAM[0x42020AE7]: 2→"2M", 4→"4M", 8→"8M"
AT+YZFACTEST sub_2309EBFA +ok=sucess,<pct> or +ok=fail,<pct> Factory test result; note firmware typo "sucess" (sic)
AT+GLTYPE sub_2309E92E +ok Stub function, always returns 0
AT+YZFLSHTEST sub_2309F694 (no args, no response) Writes 0→63 pattern to flash 0x604000, reads back, verifies; returns 0=pass, -3=fail, -1=no flash

GET/SET Commands → No Password Required

Command Handler GET Response SET Format What SET writes
AT+YZNETCHECK sub_230A1598 +ok=<val> AT+YZNETCHECK=<val> RAM[0x42020B18]
AT+YZPCHECK sub_230A1604 +ok=<val> AT+YZPCHECK=<val> RAM[0x42020B19]
AT+ADTIME sub_2309F16E +ok=<timestamp>,<mode> AT+ADTIME=<ts>,<mode> RAM[0x42021F18] = ts → RAM[0x42021F10]; triggers time update; fails if ts → current
AT+IVGET sub_2309F75E +ok=<val> AT+IVGET=<val> RAM[0x420214E0]
AT+YZFMODE sub_230A47CC +ok=<v0>,<v1>,<v2> AT+YZFMODE=<v0>,<v1>,<v2> RAM[0x42021570/74/78]; flash at 0x58F000+0x590000 (NOT yz_flash)
AT+SSLSER sub_230A17DC +ok=<host>,<client_id>,<port> AT+SSLSER=<host>,<client_id>,<port> host→RAM[0x42020A0F] (max 50 B), client_id→RAM[0x420209FF] (max 16 B), port→RAM[0x42020A44] (1→65535)
AT+MQTTSER sub_230A17DC same as SSLSER same as SSLSER Identical handler to SSLSER

Security note: AT+SSLSER / AT+MQTTSER require no password → any client on port 48899 can redirect the device's cloud connection to an arbitrary server.


SET-Only Commands → No Password Required

Command Handler SET Format Effect
AT+HTTPPOST sub_230A0510 AT+HTTPPOST=<url>,<data> Performs HTTP POST to <url> with <data>; returns +ok=<response>
AT+WEBUPURL sub_230A31A2 AT+WEBUPURL=<url> Stores OTA URL at RAM[0x42020D30] (256 bytes cleared first); triggers OTA
AT+HTTPPOSTFILE sub_230A46B6 AT+HTTPPOSTFILE=<url>,<size> POSTs flash content from 0x604000 in 1024-byte chunks to <url>
AT+YZLOG sub_2309F2AE AT+YZLOG=<op>,<sn>,<count> op=0: dump inverter registers; op=1: log query (sn must be >0); op=2→9: extended log ops
AT+INVDATA sub_2309F3DA AT+INVDATA=<reg_byte>,<inv_addr> Reads Modbus register from inverter; returns +ok=<hex bytes> or +ok=no data
AT+SDTT sub_2309F4FA AT+SDTT=<hex_bytes> Sends raw Modbus query from hex string; returns +ok=<result> or +ok=no data
AT+FILEGET sub_2309EEB2 AT+FILEGET=<proto>,<dtype>,<ftype>,<comm>,<gw_sn>,<inv_sn>,<fname>,<fpath> ≥8 args; triggers background file download; sets start flag at RAM[0x420223E4]; returns +ok=http,post

GET/SET Commands → Password Required (pwd1, default 214028)

Command Handler GET Response SET Format What SET writes
AT+YZCATTIME → sub_230A13B8 +ok=<byte> AT+YZCATTIME=214028,<N> Upload period byte at RAM[0x42020B12]
AT+YZEXPFUN sub_230A1436 +ok=<dword> AT+YZEXPFUN=214028,<N> RAM[0x42020B14] (default=3; purpose TBD)
AT+TIME sub_230A0BF6 +ok=<v1>,<v2>,<v3> AT+TIME=214028,<v1>,<v2>,<v3> RAM[0x4202094C] (1→60), RAM[0x4202094E] (1→9215), RAM[0x42020950] (1→9215)
AT+KEY / AT+YZTIME sub_230A0CC4 +ok=<val> AT+KEY=214028,<N> Writes new password to RAM[0x42020960]; N → 999999
AT+MTYPE sub_230A0D50 +ok=<val> AT+MTYPE=214028,<val> Meter type RAM[0x42020954]; val < 65536
AT+CNMOSAVE sub_230A0DD6 +ok=<idx>,<b1>,<b2>,<name>,<addr>,<port> ≥4 AT+CNMOSAVE=214028,<idx>,<b1>,<b2>,<name>,<addr>,<port> 76-byte config struct at 0x42020964 + 76*idx; idx 0→3; name → 16 B; addr → 49 B; port 1→65535
AT+DEVSELCTL sub_230A0F58 +ok=on / +ok=off AT+DEVSELCTL=214028,on or off RAM[0x42020A98] = 1 or 0
AT+DEVICENUM sub_230A1016 +ok=<count> AT+DEVICENUM=214028,<N> Connected device count RAM[0x4202094D]; N → 2
AT+NDBGS sub_230A109A +ok=<0|1> AT+NDBGS=214028,<0|1> Debug flag RAM[0x42020A99]; val must be 0 or 1
AT+FFCLEAR sub_230A1120 +ok=<val> AT+FFCLEAR=214028,1 Erases flash loop pages at [0x4200F43C..0x4200F450] step 4096; val must be exactly 1
AT+LANGSEL sub_230A11C0 +ok=<lang> AT+LANGSEL=214028,<str> 9-char language/locale string at RAM[0x42020A9A]
AT+YZPLAN sub_230A11C0 +ok=<str> AT+YZPLAN=214028,<str> Same handler as LANGSEL; same effect
AT+APSOCKET sub_230A1234 +ok=<val> AT+APSOCKET=214028,<val> RAM[0x42020AE4]
AT+YZWMODE sub_230A12B2 +ok=<mode> AT+YZWMODE=214028,<mode> WiFi mode RAM[0x42020B10]
AT+YZWAKEYCTL sub_230A1330 +ok=<val> AT+YZWAKEYCTL=214028,<val> RAM[0x42020B11]; also triggers BLE name update (sub_230A0570)
AT+YZFTIME sub_230A1670 +ok=<val>,<str> AT+YZFTIME=214028,<val>,<str> RAM[0x42020AA4] (uint32) + string at RAM[0x42020AA8]
AT+YZSPEEDREAD sub_230A1A48 +ok=<val> AT+YZSPEEDREAD=214028,<val> RAM[0x42020B48]
AT+YZSYSVER sub_230A1D5A +ok=<val> AT+YZSYSVER=214028,<val> System version RAM[0x42020B30]
AT+YZSYSCON sub_230A1DCE +ok=<val> AT+YZSYSCON=214028,<val> RAM[0x42020B20]
AT+YZDEFC sub_230A1E42 +ok=<val> AT+YZDEFC=214028,<val> Default config flag RAM[0x42020B2E]
AT+WEBCTL sub_230A1F1C +ok=<val> AT+WEBCTL=214028,<val> Web control flag RAM[0x42020B4F]
AT+SKA sub_230A1F9A +ok=<val> AT+SKA=214028,<val> RAM[0x42020B50]
AT+YZFNL sub_230A1528 +ok=<v1>,<v2> AT+YZFNL=214028,<v1>,<v2> RAM[0x42020B44] (uint16) + RAM[0x42020B46] (uint16)
AT+TS_SN sub_230A14B4 +ok=<sn> AT+TS_SN=214028,<sn> 16-char SN string at RAM[0x42020B34]
AT+YZSYSKEY sub_230A0378 +ok=<hex16> AT+YZSYSKEY=214028,<key> 16-byte system key via sub_23019440/sub_2301939C

Important: AT+KEY / AT+YZTIME (handler sub_230A0CC4) writes the new password value directly to RAM[0x42020960]. This command changes the password itself. Requires the current password as first argument.


GET/SET Commands → Password Required (same RAM[0x42020960], inline comparison)

Command Handler GET Response SET Format What SET writes
AT+ADDRESS sub_230A0686 +ok=<v1>,<v2> AT+ADDRESS=214028,<v1>,<v2> RAM[0x42020958] + RAM[0x4202095C]; triggers BLE/AP name update and flash erase 0x504000
AT+YZACK sub_230A1704 +ok=<en>,<v0>,<v1> AT+YZACK=214028,<en>,<v0>,<v1> en (0/1) → bit3 of RAM[0x420209FE]; v0 (0/1) → RAM[0x42020AD0]; v1 (0→10) → RAM[0x42020AD1]
AT+YZDBNUM sub_230A18F6 +ok=<v1>,<v2>,<v3> AT+YZDBNUM=214028,<v1>,<v2>,<v3> RAM[0x42020AD2], RAM[0x42020AD3], RAM[0x42020AD4]; note: arg[2] written to both AD3 and AD4 (bug)
AT+YZDB sub_230A1AC0 +ok=<b0>,<b1>,<b2>,<b3> AT+YZDB=214028,<b0>,<b1>,<b2>,<b3> Bits 0→3 of RAM[0x42020AD5]; also arg[4]→RAM[0x42020AD2], arg[2]→RAM[0x42020AD3/D4]
AT+APSTAT sub_230A1C22 +ok=<v1>,<v2> AT+APSTAT=214028,<v1>,<v2> RAM[0x42020AE5] + RAM[0x42020AE6]
AT+YZRSTTIME sub_230A1CBA +ok=<ADC>,<AE0> AT+YZRSTTIME=214028,<0|1>,<N> N1 (0 or 1) → RAM[0x42020ADC]; N2 → RAM[0x42020AE0] (3-hour reset period)
AT+YZDEVCTIME sub_230A199E +ok=<B4A>,<B4C> AT+YZDEVCTIME=214028,<v1>,<v2> v1 (1→9215) → RAM[0x42020B4A]; v2 (1→9215) → RAM[0x42020B4C] (default=60)
AT+SERCH sub_230A1EB6 +ok=<val> AT+SERCH=214028,<val> RAM[0x42020B1B] (signed)
AT+WKTIME sub_2309F214 +ok=<base_ts>,<secondary> AT+WKTIME=214028,<ts> RAM[0x42021F10] (wake time base); triggers NTP update
AT+YZAPP sub_2309EE46 (no GET) AT+YZAPP=214028,<net|inv> Sets app mode flag at RAM[0x4200F4E1] = 1 if mode valid, 0 otherwise
AT+ERRCLR sub_2309ECF2 (no GET) AT+ERRCLR=214028,<mode>,<sub> mode=0xFF: erase all data flash; mode=0xF0,sub=0→10: selective region erase

Special / Diagnostic Commands

Command Handler Format Notes
AT+YZFLASHREAD sub_2309EC7E GET: AT+YZFLASHREAD → +ok=<counter>; SET: AT+YZFLASHREAD=<hex_addr>,<len> → +ok=<b0>,<b1>,... No password; reads raw flash bytes at any address; e.g. AT+YZFLASHREAD=400014,8 reads password1
AT+YZEARSE sub_230A0292 AT+YZEARSE=214028,<mode>[,<sub>] mode=0xFF: erase data flash + loop pages; mode=0xF0,sub=0: erase 0x504000+0x505000; mode=0xF0,sub=1: erase loop pages; no GET
AT+IFILE sub_230A3BF0 AT+IFILE=R,<idx> or AT+IFILE=W,<idx>,<val> Read/write internal file fields by index 0→80; no password; field map in handler; index 18 reads/writes 8-byte structs

Configurable Upload Period Commands

AT+YZCATTIME → Read/Write upload period byte

Single handler (sub_230A13B8). Confirmed from decompiled C:

// GET (a2==0): returns MEMORY[0x42020B12] as %u
// SET (a2==2): password + value
if ( a2 == 2 ) {
    if ( sub_230A00DE(*a3) )            // validate password against MEMORY[0x42020960]
        MEMORY[0x42020B12] = sub_230C6026(a3[1]);
        sub_230A0BA6();                 // flash save → both flash slots
} else if ( a2 != 0 ) return -4;
GET:    AT+YZCATTIME
Reply:  +ok=<N>        (current upload period byte, default = 1)

SET:    AT+YZCATTIME=214028,<N>
Reply:  +ok            (value written and persisted to flash)

→ Confirmed working on device 192.168.88.38 with password 214028.

AT+YZEXPFUN → Read/Write 0x42020B14 word

Single handler (sub_230A1436). Confirmed from decompiled C:

// GET (a2==0): returns MEMORY[0x42020B14] as %u
// SET (a2==2): password + value
if ( a2 == 2 ) {
    if ( sub_230A00DE(*a3) )            // validate password
        MEMORY[0x42020B14] = sub_230C6026(a3[1]);
        sub_230A0BA6();                 // flash save
} else if ( a2 != 0 ) return -4;       // wrong arg count → +ERR=-4
GET:    AT+YZEXPFUN
Reply:  +ok=<MEMORY[0x42020B14]>

SET:    AT+YZEXPFUN=214028,<value>
Effect: MEMORY[0x42020B14] = value; flash saved

Note: 0x42020B14 is a different register from 0x42020B12 (upload period byte). Purpose of 0x42020B14 is unknown (default = 3 from init code). AT+YZEXPFUN is NOT the upload period → use AT+YZCATTIME for that.

AT+YZRSTTIME → Read/Write reset timers

GET and SET. Reads/writes RAM[0x42020ADC] (watchdog countdown, byte) and RAM[0x42020AE0] (3-hour reset period, dword).

GET:    AT+YZRSTTIME
Reply:  +ok=<ADC_val>,<AE0_val>

SET:    AT+YZRSTTIME=214028,<N1>,<N2>
        N1 must be 0 or 1 → stored to RAM[0x42020ADC]
        N2 → stored to RAM[0x42020AE0]
        Flash saved via sub_230A0BA6

Password = same RAM[0x42020960] (default 214028), compared inline: sub_230C6026(*a3) == MEMORY[0x42020960]

AT+YZDEVCTIME → Read/Write device timing parameters

GET and SET. Operates on RAM[0x42020B4A] (uint16) and RAM[0x42020B4C] (uint16, default=60).

GET:    AT+YZDEVCTIME
Reply:  +ok=<B4A>,<B4C>

SET:    AT+YZDEVCTIME=214028,<val1>,<val2>
        val1: 1→9215 → stored to RAM[0x42020B4A]
        val2: 1→9215 → stored to RAM[0x42020B4C]
        Flash saved via sub_230A0BA6

Password = same RAM[0x42020960] (default 214028), compared inline.

The default val2 = 60 almost certainly means 60 seconds → a 1-minute polling period or similar timing unit. This is the most likely parameter the manufacturer changes remotely to alter upload frequency.


How the Manufacturer Changes Upload Period Remotely

The manufacturer's cloud backend sends AT+ commands directly to the device via the TLS socket (YZSSL task → sub_2309FA8A). The commands are plain ASCII, prefixed AT+.

Working commands via deyeat.exe (TCP port 48899, confirmed working):

First, read current values:

.\deyeat.exe -t 192.168.88.38:48899 -xat "AT+YZCATTIME"     # +ok=1
.\deyeat.exe -t 192.168.88.38:48899 -xat "AT+YZDEVCTIME"    # +ok=65535,65535
.\deyeat.exe -t 192.168.88.38:48899 -xat "AT+YZRSTTIME"     # +ok=0,3
.\deyeat.exe -t 192.168.88.38:48899 -xat "AT+YZEXPFUN"      # +ok=<word at 0x42020B14>

Then read password from flash (single password, all commands):

# YZFLASHREAD format: first arg ignored, second arg = flash offset in decimal
# 0x400014 = 4194324
.\deyeat.exe -t 192.168.88.38:48899 -xat "AT+YZFLASHREAD=0,4194324"
# bytes[0..3] little-endian uint32 = current password value

→ CONFIRMED WORKING → AT+YZCATTIME=214028,<N>

Password 214028 is the default and has NOT been overwritten on this device. Commands verified on device at 192.168.88.38:

# Set upload period to 5
.\deyeat.exe -t 192.168.88.38:48899 -xat "AT+YZCATTIME=214028,5"
# → +ok

# Verify
.\deyeat.exe -t 192.168.88.38:48899 -xat "AT+YZCATTIME"
# → +ok=5

# Restore to default (1 = 5-minute upload cycle)
.\deyeat.exe -t 192.168.88.38:48899 -xat "AT+YZCATTIME=214028,1"
# → +ok

Known values:

  • 1 = default (5-minute cycle → the base Time300S_Event_Dispose timer is always 5 min; this byte is a multiplier)
  • Higher values = less frequent uploads (e.g. 5 = every 5×5 = 25 min, or possibly just interpreted differently by cloud)
  • The exact unit/multiplier semantics need field-testing to confirm

Note on AT+YZEXPFUN=05 → +ERR=-4: error code -4 = wrong number of arguments. Requires password,value format (2 args), not just value.


Password Values (Confirmed)

There is only one AT command password. All authenticated SET commands compare arg[0] against RAM[0x42020960].

RAM Address Flash Address Default Value Note
0x42020960 0x400014 214028 In yz_flash config block (588 bytes at 0x400000)
  • Default 214028 is hardcoded in init: MEMORY[0x42020960] = 214028 (sub_2309FDE8)
  • To change: AT+KEY=214028,<new_password> writes to RAM[0x42020960] and flashes
  • The web UI password (admin/admin, stored in HiFlying NVRAM as CFG_PWPWD) is completely independent → different storage, different mechanism, NOT used for AT commands

→??? YZFLASHREAD → Actual Command Format (Confirmed from Disassembly + Live Tests)

Handler sub_2309EC7E, line 145584:

v6 = sub_230C8208(*(unsigned __int8 **)(a3 + 4), &v7, 10);  // reads arg[1] (SECOND arg) as decimal
sub_230A92EA(v6, (int)v8, 8);                                // hardcoded 8 bytes, length arg ignored

Actual format:

AT+YZFLASHREAD=<ignored>, <decimal_flash_offset>
             → first arg  → second arg = actual flash byte offset (decimal)
  • First argument is completely ignored by the handler
  • Second argument = raw flash byte offset, base-10 decimal
  • Length is always 8 bytes (hardcoded; the "length" you pass is ignored)

All previous tests (AT+YZFLASHREAD=400014,8, AT+YZFLASHREAD=4194324,8, AT+YZFLASHREAD=0x400022,8) all had 8 as the second arg → all read flash offset 8 → BL602 firmware image FCFG header (FCFG magic = bytes 70,67,70,71 = 0x46,0x43,0x46,0x47). Always identical.

Correct commands:

Read target Flash address Correct command
yz_flash start 0x400000 = 4194304 AT+YZFLASHREAD=0,4194304
AT password (RAM[0x42020960]) 0x400014 = 4194324 AT+YZFLASHREAD=0,4194324
Upload period byte (RAM[0x42020B12]) 0x4000C6 = 4194502 AT+YZFLASHREAD=0,4194502
yz_flash backup 0x480000 = 4718592 AT+YZFLASHREAD=0,4718592

Response = 8 decimal byte values. Password = bytes[0..3] as little-endian uint32.

→ Confirmed on live device (192.168.88.38):

AT+KEY             → +ok=214028          (reads RAM[0x42020960] directly)
AT+YZFLASHREAD=0,4194324 → +ok=12,68,3,0,0,2,20,52
  bytes[0..3] LE: 12 + 68×256 + 3×65536 + 0 = 214028  → matches RAM

RAM value = flash value → password has NOT been overwritten after SolarMan registration on this device.


CFG_SDC → How to Read It

CFG_SDC (index 15 in the off_230F31A8 key table) is not a standalone HTTP endpoint. It is embedded by the template engine into the HTML page at load time.

Correct method:

  1. GET http://10.10.100.100/index_cn.html
  2. Parse var CFG_SDC = "..."; from the JavaScript block

Python example:

import requests, re
r = requests.get("http://10.10.100.100/index_cn.html")
m = re.search(r'var CFG_SDC = "([^"]+)"', r.text)
if m:
    parts = m.group(1).split(",")  # format: "device_type,da_id,protocol_ver"
    da_id = int(parts[1])          # Modbus slave address (= 162 = 0xA2)

CFG_SDC format: "<device_type>,<da_id>,<protocol_ver>" where da_id is the Modbus slave address used internally (confirmed 162).


Modbus Slave ID → How to Get It

The Modbus RS-485 slave address of the EV charger is NOT exposed via any AT command.

Why AT+ADDRESS is not it

AT+ADDRESS GET returns RAM[0x42020958] and RAM[0x4202095C] → both contain the device SN (3422438570). This is the cloud device identifier, not the RS-485 slave address.

Why AT+DEVICENUM is not it

AT+DEVICENUM GET returns RAM[0x4202094D] → the number of connected inverter slots (0→2). Although CFG_SDC writes da_id to this same address (sub_230A64D4, line 151851), AT+DEVICENUM SET is clamped to → 2, and AT+DEVICENUM on port 48899 returns the UDP discovery response instead of the AT response on this device.

Where the slave address actually lives

The RS-485 slave address 162 (0xA2) is hardcoded as a literal in sub_230B29BC → the Modbus write function called from CFG_CHARGE_POWER. It is not stored in the yz_flash config block and is not readable or writable via any AT command.

How to get it

Method Command / Path Result
Web UI (most reliable) GET http://10.10.100.100/index_cn.html → parse var CFG_SDC = "..." → field[1] da_id value
From firmware Hardcoded in sub_230B29BC 162 (0xA2)
AT commands ??? Not available →

How SolarMan cloud gets device hierarchy

Cloud does not query slave addresses. The module itself PUSH-es inverter data via the TLS socket (YZSSL task). The cloud hierarchy is:

  • Gateway = the Wi-Fi logger module, identified by GWSN=<SN> (e.g. 3422438570)
  • Device = the EV charger, identified by yz_flash.da_id (RAM[0x42020958]) which the module reads from the charger via Modbus at registration time and stores in yz_flash

The RS-485 slave address 162 is purely a transport-layer bus address, invisible to the cloud. The cloud device ID (da_id) is a separate application-layer identifier written into RAM[0x42020958] → readable via AT+ADDRESS GET (first value in the pair).


UDP Device Discovery

The device responds to UDP broadcast on port 48899:

Message Response Fields
WIFIKIT-214028-READ <IP>,<MAC>,<SN> IP address, MAC, serial number
HF-A11ASSISTHREAD <IP>,<MAC>,<DeviceName> IP address, MAC, device name

Python code:

import socket, time

DISCOVERY_PORT = 48899
DISCOVERY_MESSAGES = [b"WIFIKIT-214028-READ", b"HF-A11ASSISTHREAD"]

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.settimeout(0.5)

for msg in DISCOVERY_MESSAGES:
    sock.sendto(msg, ("255.255.255.255", DISCOVERY_PORT))
    try:
        data, addr = sock.recvfrom(256)
        print(f"Response to {msg}: {data.decode()} from {addr[0]}")
    except socket.timeout:
        pass
sock.close()

5-Minute Upload State Machine

The Time300S_Event_Dispose function drives the cloud upload cycle. States:

State Name Action
0 sInverter300msEventStart Start 5-min timer
1 sComplexWifiTCPLKBCmd Check WiFi/TCP connection
2 sComplexWifiIPSTCPCmd Send IP/TCP command
3 sComplexInverterMemoryWriteCmd Write inverter memory
4 sComplexServerACKTimeOut Wait for server ACK
5 sComplexWifiEndDataCmd End data sequence
6 sComplexServerCmdTimeOut Server command timeout
7 sComplexInverterErrorCmd Handle inverter error

Key log: "Time300S_Event_Dispose##cyg_12hors_delays=%0x,yz_flash.yz_timed_start.enable=%0x" → the yz_timed_start.enable field in the yz_flash block at 0x42021570 controls whether the scheduled upload is active.


What Is Hardcoded vs. Configurable

Parameter Location Default Configurable? How
5-min upload cycle timer Time300S_Event_Dispose state machine 300 s No Hardcoded in RTOS timer
Upload period byte RAM[0x42020B12] 1 Yes AT+YZCATTIME=214028,<N> → confirmed
Device timing params RAM[0x42020B4A], 0x42020B4C ?, 60 Yes AT+YZDEVCTIME=<pwd>,X,Y
3-hour reset period RAM[0x42020AE0] 3 Yes AT+YZRSTTIME=<pwd>,N1,N2
Schedule enable flag yz_timed_start.enable in yz_flash ? Possibly Via yz_flash flash block write
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment