├── LICENSE ├── README.md ├── Temp └── mc3_gsm.go ├── commands.go └── sim900.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Hugo Arganda 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 | # SIM900 Go's package 2 | This package uses a serialport to communicate with the SIM900 GSM Modem. 3 | 4 | ## How to install 5 | 6 | - You'll need Golang v1.3+ 7 | - SIM900 Package uses the [serial](https://github.com/argandas/serial) package in order to communicate with the modem via AT commands, you will need to install both SIM900 and serial packages. 8 | 9 | ```bash 10 | go get github.com/argandas/serial # installs the serial package 11 | go get github.com/argandas/sim900 # installs the SIM900 package 12 | ``` 13 | 14 | ## How to use 15 | 16 | - You'll need an available serial port, SIM900 boards usually works with 5V TTL signals so you can get a USB-to-Serial TTL converter, I recommend you to use the [FTDI Cable](https://www.sparkfun.com/products/9718) for this, but you can use any USB-to-Serial adapters there are plenty of them. 17 | ![SIM900: FTDI Cable](TBD) 18 | 19 | - Connect carefuly your serialport to your SIM900 board. 20 | ![SIM900: Connection diagram](TBD) 21 | 22 | ## Example code 23 | 24 | ```go 25 | package main 26 | import "github.com/argandas/sim900" 27 | 28 | func main() { 29 | gsm := sim900.New() 30 | err := gsm.Connect("COM1", 9600) 31 | if err != nil { 32 | panic(err) 33 | } 34 | defer gsm.Disconnect() 35 | phoneNumber := "XXXXXXXXXX" // The number to send the SMS 36 | gsm.SendSMS(phoneNumber, "Hello World!") 37 | } 38 | ``` 39 | 40 | ## Reference 41 | 42 | - List of available SIM900 commands can be found [here](http://wm.sim.com/upfile/2013424141114f.pdf). 43 | - For more information about available SIM900 methods please check godoc for this package. 44 | 45 | Go explore! 46 | 47 | ## License 48 | 49 | SIM900 package is MIT-Licensed 50 | -------------------------------------------------------------------------------- /Temp/mc3_gsm.go: -------------------------------------------------------------------------------- 1 | package mc3 2 | 3 | import ( 4 | "github.com/mitchellh/mapstructure" 5 | "encoding/json" 6 | "regexp" 7 | "errors" 8 | "time" 9 | "fmt" 10 | ) 11 | 12 | const( 13 | CMD_ERROR_REGEXP string = "(^ERROR$)" 14 | CMD_OK_REGEXP string = "(^OK$)" 15 | CMD_AT string = "AT" 16 | CMD_CMGF string = "AT+CMGF=1" 17 | CMD_CMGS string = "AT+CMGS=\"%s\"" 18 | CMD_CTRL_Z string = "\x1A" 19 | CMD_CMGS_RX_REGEXP string = "(^[+]CMGS[:] [0-9]+$)" 20 | CMD_CMTI_REGEXP string = "(^[+]CMTI[:] \"SM\",[0-9]+$)" 21 | CMD_CMTI_RX string = "+CMTI: \"SM\"," 22 | CMD_CMGR string = "AT+CMGR=%s" 23 | CMD_CMGR_REGEXP string = "(^[+]CMGR[:] .*)" 24 | CMD_CMGR_RX string = "+CMGR: " 25 | ) 26 | 27 | /******************************************************************************************* 28 | **************************** GSM: TYPE DEFINITIONS ************************************ 29 | *******************************************************************************************/ 30 | 31 | var( 32 | GSM_portOpen bool = false 33 | GSM_sp sp_config_t 34 | ) 35 | 36 | /******************************************************************************************* 37 | ******************************** GSM: BASIC FUNCTIONS *********************************** 38 | *******************************************************************************************/ 39 | 40 | // This function opens the selected COMPort (name), at a configurable Baud Rate (br). 41 | // The common used baud rate is 115200 but this can change. 42 | // mc3 := NewMC3() 43 | // err := mc3.GSM_OpenPort("COM1", 115200) 44 | // if err != nil { 45 | // // Port Open NOK. Maybe log that error?. Do things 46 | // } else { 47 | // // Port Open OK. Enjoy! 48 | // } 49 | func (mc3 *MC3) GSM_OpenPort(name string, br uint32) (err error) { 50 | //Marshal Serial port configuration before calling Serial Port PIWI 51 | var portConfig = sp_config_t { Port: name, Baudrate: br } 52 | json_2_send, e := json.Marshal(portConfig) 53 | if e != nil { 54 | err = e 55 | } else { 56 | //Call "Open" Method from Serial Port PIWI, return port status (0.-Closed, 1.-Open*, 2.-Error ) 57 | var payload_rx piwiError 58 | err = mapstructure.Decode(mc3.model.Call("serialport", "Open", json_2_send), &payload_rx) 59 | if err != nil { 60 | // Do nothing 61 | } else { 62 | err = payload_rx.GetError() 63 | if err != nil { 64 | // Do nothing 65 | } else { 66 | GSM_portOpen = true 67 | GSM_sp = portConfig 68 | mc3.Log("INF","GSM","Serial port open.") 69 | } 70 | } 71 | } 72 | mc3.gsm_log_error(err) 73 | return 74 | } 75 | 76 | // This function close the GSM COM Port ( previously defined by GSM_OpenPort). 77 | // mc3 := NewMC3() 78 | // err := mc3.GSM_OpenPort("COM1", 115200) 79 | // if err != nil { 80 | // // Port Open NOK. Maybe log that error?. Do things 81 | // } else { 82 | // // Port Open OK. Enjoy! 83 | // // Do things 84 | // mc3.GSM_ClosePort() 85 | // } 86 | // NOTE: If you have Opened the GSM Serial Port you must use the GSM_ClosePort function to prevent future errors. 87 | func (mc3 *MC3) GSM_ClosePort() (err error) { 88 | if GSM_portOpen { 89 | //Call "Open" Method from Serial Port PIWI, return port status (0.-Closed, 1.-Open*, 2.-Error ) 90 | var payload_rx piwiError 91 | err = mapstructure.Decode(mc3.model.Call("serialport", "Close", uint8(0)), &payload_rx) 92 | if err != nil { 93 | // Do nothing 94 | } else { 95 | err = payload_rx.GetError() 96 | if err != nil { 97 | // Do nothing 98 | } else { 99 | GSM_portOpen = false 100 | GSM_sp.Port = "" 101 | GSM_sp.Baudrate = 0 102 | mc3.Log("INF","GSM", "Serial port closed.") 103 | } 104 | } 105 | } else { 106 | err = errors.New("Unable to Close Port, Serial Port is not open.") 107 | } 108 | mc3.gsm_log_error(err) 109 | return 110 | } 111 | 112 | /* 113 | This function call the "Write" method from "serialport" PIWI. 114 | Returns nil if the serial port write was succesful, otherwise returns an error. 115 | */ 116 | func (mc3 *MC3) GSM_print(data string) (err error) { 117 | var payload_rx piwiError 118 | mapstructure.Decode(mc3.model.Call("serialport", "Write", data), &payload_rx) 119 | err = payload_rx.GetError() 120 | if err != nil { 121 | // Do nothing 122 | } else { 123 | mc3.Log("INF","GSM","Tx >> " + data) 124 | } 125 | mc3.gsm_log_error(err) 126 | return 127 | } 128 | 129 | /* 130 | This function call the "WriteLine" method from "serialport" PIWI. 131 | Returns nil if the serial port write was succesful, otherwise returns an error. 132 | */ 133 | func (mc3 *MC3) GSM_println(data string) (err error) { 134 | var payload_rx piwiError 135 | mapstructure.Decode(mc3.model.Call("serialport", "WriteLine", data), &payload_rx) 136 | err = payload_rx.GetError() 137 | if err != nil { 138 | // Do nothing 139 | } else { 140 | mc3.Log("INF","GSM","Tx >> " + data) 141 | } 142 | mc3.gsm_log_error(err) 143 | return 144 | } 145 | 146 | /* 147 | This function writes a value to the specified EID (both string format). 148 | Example: To write 20099 (integer value) to the EID 0x41B should be written like 149 | GSM_WriteEID("0x41B","20099") 150 | Example: For string values they must be written as raw string literals (between back quotes ``): 151 | GSM_WriteEID("0x41A",`"thunderfish.moreycorp.com"`) 152 | */ 153 | func (mc3 *MC3) GSM_SendSMS(number, msg string) (echo string, err error) { 154 | _, err = mc3.GSM_CheckSMSMode() 155 | if err != nil { 156 | // Do nothing 157 | } else { 158 | cmd := fmt.Sprintf(CMD_CMGS + "\r\n", number) 159 | err = mc3.GSM_print(cmd) 160 | if err != nil { 161 | // Do nothing 162 | } else { 163 | str, error_found := mc3.GSM_WaitForRegexTimeout(CMD_ERROR_REGEXP, time.Second * 1 ) 164 | if !error_found { 165 | cmd := msg + CMD_CTRL_Z 166 | err = mc3.GSM_print(cmd) 167 | if err != nil { 168 | // Do nothing 169 | } else { 170 | str, valid := mc3.GSM_WaitForRegexTimeout(CMD_CMGS_RX_REGEXP + "|" + CMD_ERROR_REGEXP, time.Second * 10 ) 171 | if valid { 172 | mc3.Log("INF","GSM","Rx << " + str) 173 | // Check if there is an update in progress 174 | error, _ := regexp.Match("ERROR", []byte(str)) 175 | if !error { 176 | echo = str 177 | } else { 178 | // Wait for update to be done 179 | err = errors.New("CMD ERROR: " + cmd + " >> " + str) 180 | } 181 | } 182 | } 183 | } else { 184 | err = errors.New("CMD ERROR: " + cmd + " >> " + str) 185 | } 186 | } 187 | mc3.gsm_log_error(err) 188 | } 189 | return 190 | } 191 | 192 | func (mc3 *MC3) GSM_WaitSMS(timeout time.Duration) (id string, err error) { 193 | _, err = mc3.GSM_CheckSMSMode() 194 | if err != nil { 195 | // Do nothing 196 | } else { 197 | str, found := mc3.GSM_WaitForRegexTimeout(CMD_CMTI_REGEXP, timeout) 198 | if found { 199 | if len(str) > len(CMD_CMTI_RX) { 200 | id = str[len(CMD_CMTI_RX):] 201 | } 202 | } else { 203 | err = errors.New("Timeout expired") 204 | } 205 | mc3.gsm_log_error(err) 206 | } 207 | return 208 | } 209 | 210 | func (mc3 *MC3) GSM_ReadSMS(id string) (msg string, err error) { 211 | _, err = mc3.GSM_CheckSMSMode() 212 | if err != nil { 213 | // Do nothing 214 | } else { 215 | cmd := fmt.Sprintf(CMD_CMGR + "\r\n", id) 216 | err = mc3.GSM_print(cmd) 217 | if err != nil { 218 | // Do nothing 219 | } else { 220 | _, found := mc3.GSM_WaitForRegexTimeout(CMD_CMGR_REGEXP, time.Second * 10) 221 | if found { 222 | // if len(str) > len(CMD_CMGR_RX) { 223 | // msg = str[len(CMD_CMGR_RX):] 224 | // } 225 | msg, _ = mc3.GSM_WaitForRegexTimeout(".*", time.Second * 1) 226 | } else { 227 | err = errors.New("Timeout expired") 228 | } 229 | mc3.gsm_log_error(err) 230 | } 231 | } 232 | return 233 | } 234 | 235 | func (mc3 *MC3) GSM_CheckSMSMode() (echo string, err error) { 236 | _, err = mc3.GSM_Ping() 237 | if err != nil { 238 | // Do nothing 239 | } else { 240 | cmd := CMD_CMGF + "\r\n" 241 | err = mc3.GSM_print(cmd) 242 | if err != nil { 243 | // Do nothing 244 | } else { 245 | str, valid := mc3.GSM_WaitForRegexTimeout(CMD_OK_REGEXP + "|" + CMD_ERROR_REGEXP, time.Second * 1 ) 246 | if valid { 247 | mc3.Log("INF","GSM","Rx << " + str) 248 | // Check if there is an update in progress 249 | error, _ := regexp.Match("ERROR", []byte(str)) 250 | if !error { 251 | echo = str 252 | } else { 253 | // Wait for update to be done 254 | err = errors.New("CMD ERROR: " + cmd + " >> " + str) 255 | } 256 | } 257 | } 258 | mc3.gsm_log_error(err) 259 | } 260 | return 261 | } 262 | 263 | func (mc3 *MC3) GSM_Ping() (echo string, err error) { 264 | cmd := CMD_AT 265 | err = mc3.GSM_print(cmd + "\r\n") 266 | if err != nil { 267 | // Do nothing 268 | } else { 269 | str, valid := mc3.GSM_WaitForRegexTimeout(CMD_OK_REGEXP + "|" + CMD_ERROR_REGEXP, time.Second * 1 ) 270 | if valid { 271 | echo = str 272 | mc3.Log("INF","GSM","Rx << " + str) 273 | // Check if there is an update in progress 274 | error, _ := regexp.Match("ERROR", []byte(str)) 275 | if error { 276 | err = errors.New("CMD ERROR: " + cmd + " >> " + str) 277 | } 278 | } 279 | } 280 | mc3.gsm_log_error(err) 281 | return 282 | } 283 | 284 | /* 285 | This function wait for a regular expression to be received by the "serialport" PIWI including a timeout 286 | Is recommended to use this function instead of "CLI_WaitForRegex" 287 | Returns 2 parameters: 288 | string: The string in wich the RegExp was found 289 | bool: Will return true if the RegExp was found 290 | */ 291 | func (mc3 *MC3) GSM_WaitForRegexTimeout(regexp string, timeout time.Duration) (string, bool) { 292 | //Encode data to send 293 | var data_tx = Regexp_Timeout_t { 294 | regexp, 295 | timeout, 296 | } 297 | payload_tx, _ := json.Marshal(data_tx) 298 | 299 | //Wait for RegEx from Serialport 300 | payload_rx := mc3.model.Call("serialport", "WaitForRegexTimeout", payload_tx).(interface{}) 301 | 302 | //Decode received data 303 | var data_rx Regexp_Response_t 304 | json.Unmarshal(payload_rx.([]byte), &data_rx) 305 | return data_rx.Regexp, data_rx.Found 306 | } 307 | 308 | func (mc3 *MC3) gsm_log_error(e error) { 309 | if e != nil { 310 | mc3.Log("ERR","GSM", e.Error()) 311 | } 312 | } -------------------------------------------------------------------------------- /commands.go: -------------------------------------------------------------------------------- 1 | package sim900 2 | 3 | // AT commands 4 | const ( 5 | CMD_AT string = "AT" 6 | CMD_OK string = "(^OK$)" 7 | CMD_ERROR string = "(^ERROR$)" 8 | CMD_CMGF string = "AT+CMGF?" 9 | CMD_CMGF_SET string = "AT+CMGF=%s" 10 | CMD_CMGF_REGEXP string = "(^[+]CMGF[:] [0-9]+$)" 11 | CMD_CMGF_RX string = "+CMGF: " 12 | CMD_CTRL_Z string = "\x1A" 13 | CMD_CMGS string = "AT+CMGS=\"%s\"" 14 | CMD_CMGS_RX_REGEXP string = "(^[+]CMGS[:] [0-9]+$)" 15 | CMD_CMGD string = "AT+CMGD=%s" 16 | CMD_CMGR string = "AT+CMGR=%s" 17 | CMD_CMGR_REGEXP string = "(^[+]CMGR[:] .*)" 18 | CMD_CMGR_RX string = "+CMGR: " 19 | CMD_CMTI_REGEXP string = "(^[+]CMTI[:] \"SM\",[0-9]+$)" 20 | CMD_CMTI_RX string = "+CMTI: \"SM\"," 21 | ) 22 | 23 | // SMS Message Format 24 | const ( 25 | PDU_MODE string = "0" 26 | TEXT_MODE string = "1" 27 | ) 28 | -------------------------------------------------------------------------------- /sim900.go: -------------------------------------------------------------------------------- 1 | package sim900 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/argandas/serial" 7 | "log" 8 | "os" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | /******************************************************************************************* 14 | ******************************** TYPE DEFINITIONS ************************************ 15 | *******************************************************************************************/ 16 | 17 | // A SIM900 is the representation of a SIM900 GSM modem with several utility features. 18 | type SIM900 struct { 19 | port *serial.SerialPort 20 | logger *log.Logger 21 | } 22 | 23 | /******************************************************************************************* 24 | ******************************** GSM: BASIC FUNCTIONS *********************************** 25 | *******************************************************************************************/ 26 | 27 | // New creates and initializes a new SIM900 device. 28 | func New() *SIM900 { 29 | return &SIM900{ 30 | port: serial.New(), 31 | logger: log.New(os.Stdout, "[sim900] ", log.LstdFlags), 32 | } 33 | } 34 | 35 | // Connect creates a connection with the SIM900 modem via serial port and test communications. 36 | func (s *SIM900) Connect(port string, baud int) error { 37 | // Open device serial port 38 | if err := s.port.Open(port, baud, time.Millisecond*100); err != nil { 39 | return err 40 | } 41 | // Ping to Modem 42 | return s.Ping() 43 | } 44 | 45 | func (sim *SIM900) Disconnect() error { 46 | // Close device serial port 47 | return sim.port.Close() 48 | } 49 | 50 | func (sim *SIM900) wait4response(cmd, expected string, timeout time.Duration) (string, error) { 51 | // Send command 52 | if err := sim.port.Println(cmd); err != nil { 53 | return "", err 54 | } 55 | // Wait for command response 56 | regexp := expected + "|" + CMD_ERROR 57 | response, err := sim.port.WaitForRegexTimeout(regexp, timeout) 58 | if err != nil { 59 | return "", err 60 | } 61 | // Check if response is an error 62 | if strings.Contains(response, "ERROR") { 63 | return response, errors.New("Errors found on command response") 64 | } 65 | // Response received succesfully 66 | return response, nil 67 | } 68 | 69 | // Send a SMS 70 | func (s *SIM900) SendSMS(number, msg string) error { 71 | // Set message format 72 | if err := s.SetSMSMode(TEXT_MODE); err != nil { 73 | return err 74 | } 75 | // Send command 76 | cmd := fmt.Sprintf(CMD_CMGS, number) 77 | if err := s.port.Println(cmd); err != nil { 78 | return err 79 | } 80 | // Wait modem to be ready 81 | time.Sleep(time.Second * 1) 82 | // Send message 83 | _, err := s.wait4response(msg+CMD_CTRL_Z, CMD_OK, time.Second*5) 84 | if err != nil { 85 | return err 86 | } 87 | // Message sent succesfully 88 | return nil 89 | } 90 | 91 | // WaitSMS will return when either a new SMS is recived or the timeout has expired. 92 | // The returned value is the memory ID of the received SMS, use ReadSMS to read SMS content. 93 | func (s *SIM900) WaitSMS(timeout time.Duration) (id string, err error) { 94 | id, err = s.wait4response("", CMD_CMTI_REGEXP, timeout) 95 | if err != nil { 96 | return 97 | } 98 | if len(id) >= len(CMD_CMTI_RX) { 99 | id = id[len(CMD_CMTI_RX):] 100 | } 101 | return 102 | } 103 | 104 | // ReadSMS retrieves SMS text from inbox memory by ID. 105 | func (s *SIM900) ReadSMS(id string) (msg string, err error) { 106 | // Set message format 107 | if err := s.SetSMSMode(TEXT_MODE); err != nil { 108 | return "", err 109 | } 110 | // Send command 111 | cmd := fmt.Sprintf(CMD_CMGR, id) 112 | if _, err := s.wait4response(cmd, CMD_CMGR_REGEXP, time.Second*5); err != nil { 113 | return "", err 114 | } 115 | // Reading succesful get message data 116 | return s.port.ReadLine() 117 | } 118 | 119 | // ReadSMS deletes SMS from inbox memory by ID. 120 | func (s *SIM900) DeleteSMS(id string) error { 121 | // Send command 122 | cmd := fmt.Sprintf(CMD_CMGD, id) 123 | _, err := s.wait4response(cmd, CMD_OK, time.Second*1) 124 | return err 125 | } 126 | 127 | // Ping modem 128 | func (s *SIM900) Ping() error { 129 | _, err := s.wait4response(CMD_AT, CMD_OK, time.Second*1) 130 | return err 131 | } 132 | 133 | // SetSMSMode selects SMS Message Format ("0" = PDU mode, "1" = Text mode) 134 | func (s *SIM900) SetSMSMode(mode string) error { 135 | cmd := fmt.Sprintf(CMD_CMGF_SET, mode) 136 | _, err := s.wait4response(cmd, CMD_OK, time.Second*1) 137 | return err 138 | } 139 | 140 | // SetSMSMode reads SMS Message Format (0 = PDU mode, 1 = Text mode) 141 | func (s *SIM900) SMSMode() (mode string, err error) { 142 | mode, err = s.wait4response(CMD_CMGF, CMD_CMGF_REGEXP, time.Second*1) 143 | if err != nil { 144 | return 145 | } 146 | if len(mode) >= len(CMD_CMGF_RX) { 147 | mode = mode[len(CMD_CMGF_RX):] 148 | } 149 | return 150 | } 151 | 152 | // SetSMSMode selects SMS Message Format (0 = PDU mode, 1 = Text mode) 153 | func (s *SIM900) CheckSMSTextMode(mode int) error { 154 | cmd := fmt.Sprintf(CMD_CMGF, mode) 155 | _, err := s.wait4response(cmd, CMD_OK, time.Second*1) 156 | return err 157 | } 158 | --------------------------------------------------------------------------------